Improve block handling so references can be added [#21]
This commit is contained in:
parent
9b819ced63
commit
26bc3acd3e
|
@ -7,7 +7,11 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
const end = {type: '', suggest: '\n', then: {}};
|
||||
const hiddenEnd = {type: '', then: {}};
|
||||
|
||||
const textToEnd = {type: 'string', then: {'': 0, '\n': end}};
|
||||
function textTo(exit) {
|
||||
return {type: 'string', then: Object.assign({'': 0}, exit)};
|
||||
}
|
||||
|
||||
const textToEnd = textTo({'\n': end});
|
||||
const aliasListToEnd = {type: 'variable', suggest: 'Agent', then: {
|
||||
'': 0,
|
||||
'as': {type: 'keyword', suggest: true, then: {
|
||||
|
@ -20,11 +24,17 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
',': {type: 'operator', suggest: true, then: {'': 1}},
|
||||
'\n': end,
|
||||
}};
|
||||
const agentListToText = {type: 'variable', suggest: 'Agent', then: {
|
||||
|
||||
function agentListTo(exit) {
|
||||
return {type: 'variable', suggest: 'Agent', then: Object.assign({
|
||||
'': 0,
|
||||
',': {type: 'operator', suggest: true, then: {'': 1}},
|
||||
}, exit)};
|
||||
}
|
||||
|
||||
const agentListToText = agentListTo({
|
||||
':': {type: 'operator', suggest: true, then: {'': textToEnd}},
|
||||
}};
|
||||
});
|
||||
const agentList2ToText = {type: 'variable', suggest: 'Agent', then: {
|
||||
'': 0,
|
||||
',': {type: 'operator', suggest: true, then: {'': agentListToText}},
|
||||
|
@ -43,6 +53,23 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
}},
|
||||
'\n': end,
|
||||
}};
|
||||
const referenceName = {
|
||||
':': {type: 'operator', suggest: true, then: {
|
||||
'': textTo({
|
||||
'as': {type: 'keyword', suggest: true, then: {
|
||||
'': {type: 'variable', suggest: 'Agent', then: {
|
||||
'': 0,
|
||||
'\n': end,
|
||||
}},
|
||||
}},
|
||||
}),
|
||||
}},
|
||||
};
|
||||
const refDef = {type: 'keyword', suggest: true, then: Object.assign({
|
||||
'over': {type: 'keyword', suggest: true, then: {
|
||||
'': agentListTo(referenceName),
|
||||
}},
|
||||
}, referenceName)};
|
||||
|
||||
function makeSideNote(side) {
|
||||
return {
|
||||
|
@ -158,6 +185,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
}},
|
||||
'begin': {type: 'keyword', suggest: true, then: {
|
||||
'': aliasListToEnd,
|
||||
'reference': refDef,
|
||||
'as': CM_ERROR,
|
||||
}},
|
||||
'end': {type: 'keyword', suggest: true, then: {
|
||||
|
|
|
@ -213,6 +213,10 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
this.currentNest = null;
|
||||
|
||||
this.stageHandlers = {
|
||||
'block begin': this.handleBlockBegin.bind(this),
|
||||
'block split': this.handleBlockSplit.bind(this),
|
||||
'block end': this.handleBlockEnd.bind(this),
|
||||
'group begin': this.handleGroupBegin.bind(this),
|
||||
'mark': this.handleMark.bind(this),
|
||||
'async': this.handleAsync.bind(this),
|
||||
'agent define': this.handleAgentDefine.bind(this),
|
||||
|
@ -224,9 +228,6 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
'note left': this.handleNote.bind(this),
|
||||
'note right': this.handleNote.bind(this),
|
||||
'note between': this.handleNote.bind(this),
|
||||
'block begin': this.handleBlockBegin.bind(this),
|
||||
'block split': this.handleBlockSplit.bind(this),
|
||||
'block end': this.handleBlockEnd.bind(this),
|
||||
};
|
||||
this.handleStage = this.handleStage.bind(this);
|
||||
this.convertAgent = this.convertAgent.bind(this);
|
||||
|
@ -365,22 +366,23 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
const agents = [leftAgent, rightAgent];
|
||||
const stages = [];
|
||||
this.currentSection = {
|
||||
header: {
|
||||
type: 'block begin',
|
||||
mode,
|
||||
label,
|
||||
stages,
|
||||
left: leftAgent.name,
|
||||
right: rightAgent.name,
|
||||
ln,
|
||||
},
|
||||
stages,
|
||||
};
|
||||
this.currentNest = {
|
||||
mode,
|
||||
agents,
|
||||
leftAgent,
|
||||
rightAgent,
|
||||
hasContent: false,
|
||||
stage: {
|
||||
type: 'block',
|
||||
sections: [this.currentSection],
|
||||
left: leftAgent.name,
|
||||
right: rightAgent.name,
|
||||
},
|
||||
};
|
||||
this.agentStates.set(leftAgent.name, LOCKED_AGENT);
|
||||
this.agentStates.set(rightAgent.name, LOCKED_AGENT);
|
||||
|
@ -389,6 +391,69 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
return {agents, stages};
|
||||
}
|
||||
|
||||
handleBlockBegin({ln, mode, label}) {
|
||||
const name = '__BLOCK' + this.blockCount;
|
||||
this.beginNested(mode, label, name, ln);
|
||||
++ this.blockCount;
|
||||
}
|
||||
|
||||
handleBlockSplit({ln, mode, label}) {
|
||||
if(this.currentNest.mode !== 'if') {
|
||||
throw new Error(
|
||||
'Invalid block nesting ("else" inside ' +
|
||||
this.currentNest.mode + ')'
|
||||
);
|
||||
}
|
||||
optimiseStages(this.currentSection.stages);
|
||||
this.currentSection = {
|
||||
header: {
|
||||
type: 'block split',
|
||||
mode,
|
||||
label,
|
||||
left: this.currentNest.leftAgent.name,
|
||||
right: this.currentNest.rightAgent.name,
|
||||
ln,
|
||||
},
|
||||
stages: [],
|
||||
};
|
||||
this.currentNest.sections.push(this.currentSection);
|
||||
}
|
||||
|
||||
handleBlockEnd() {
|
||||
if(this.nesting.length <= 1) {
|
||||
throw new Error('Invalid block nesting (too many "end"s)');
|
||||
}
|
||||
optimiseStages(this.currentSection.stages);
|
||||
const nested = this.nesting.pop();
|
||||
this.currentNest = array.last(this.nesting);
|
||||
this.currentSection = array.last(this.currentNest.sections);
|
||||
|
||||
if(nested.hasContent) {
|
||||
this.defineAgents(nested.agents);
|
||||
addBounds(
|
||||
this.agents,
|
||||
nested.leftAgent,
|
||||
nested.rightAgent,
|
||||
nested.agents
|
||||
);
|
||||
nested.sections.forEach((section) => {
|
||||
this.currentSection.stages.push(section.header);
|
||||
this.currentSection.stages.push(...section.stages);
|
||||
});
|
||||
this.addStage({
|
||||
type: 'block end',
|
||||
left: nested.leftAgent.name,
|
||||
right: nested.rightAgent.name,
|
||||
});
|
||||
} else {
|
||||
throw new Error('Empty block');
|
||||
}
|
||||
}
|
||||
|
||||
handleGroupBegin() {
|
||||
throw new Error('Groups are not supported yet');
|
||||
}
|
||||
|
||||
handleMark({name}) {
|
||||
this.markers.add(name);
|
||||
this.addStage({type: 'mark', name}, false);
|
||||
|
@ -524,54 +589,14 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
]);
|
||||
}
|
||||
|
||||
handleBlockBegin({ln, mode, label}) {
|
||||
const name = '__BLOCK' + this.blockCount;
|
||||
this.beginNested(mode, label, name, ln);
|
||||
++ this.blockCount;
|
||||
}
|
||||
|
||||
handleBlockSplit({ln, mode, label}) {
|
||||
const containerMode = this.currentNest.stage.sections[0].mode;
|
||||
if(containerMode !== 'if') {
|
||||
throw new Error(
|
||||
'Invalid block nesting ("else" inside ' +
|
||||
containerMode + ')'
|
||||
);
|
||||
}
|
||||
optimiseStages(this.currentSection.stages);
|
||||
this.currentSection = {
|
||||
mode,
|
||||
label,
|
||||
stages: [],
|
||||
ln,
|
||||
};
|
||||
this.currentNest.stage.sections.push(this.currentSection);
|
||||
}
|
||||
|
||||
handleBlockEnd() {
|
||||
if(this.nesting.length <= 1) {
|
||||
throw new Error('Invalid block nesting (too many "end"s)');
|
||||
}
|
||||
optimiseStages(this.currentSection.stages);
|
||||
const nested = this.nesting.pop();
|
||||
this.currentNest = array.last(this.nesting);
|
||||
this.currentSection = array.last(this.currentNest.stage.sections);
|
||||
if(nested.hasContent) {
|
||||
this.defineAgents(nested.agents);
|
||||
addBounds(
|
||||
this.agents,
|
||||
nested.leftAgent,
|
||||
nested.rightAgent,
|
||||
nested.agents
|
||||
);
|
||||
this.addStage(nested.stage);
|
||||
}
|
||||
}
|
||||
|
||||
handleStage(stage) {
|
||||
this.latestLine = stage.ln;
|
||||
try {
|
||||
this.stageHandlers[stage.type](stage);
|
||||
const handler = this.stageHandlers[stage.type];
|
||||
if(!handler) {
|
||||
throw new Error('Unknown command: ' + stage.type);
|
||||
}
|
||||
handler(stage);
|
||||
} catch(e) {
|
||||
if(typeof e === 'object' && e.message) {
|
||||
throw new Error(e.message + ' at line ' + (stage.ln + 1));
|
||||
|
@ -594,7 +619,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
if(this.nesting.length !== 1) {
|
||||
throw new Error(
|
||||
'Unterminated section at line ' +
|
||||
(this.currentSection.ln + 1)
|
||||
(this.currentSection.header.ln + 1)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,14 +22,25 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
return {type: 'block split', mode, label, ln};
|
||||
},
|
||||
|
||||
blockEnd: () => {
|
||||
return {type: 'block end'};
|
||||
blockEnd: ({ln = 0} = {}) => {
|
||||
return {type: 'block end', ln};
|
||||
},
|
||||
|
||||
labelPattern: (pattern, {ln = 0} = {}) => {
|
||||
return {type: 'label pattern', pattern, ln};
|
||||
},
|
||||
|
||||
groupBegin: (alias, agentNames, {label = '', ln = 0} = {}) => {
|
||||
return {
|
||||
type: 'group begin',
|
||||
agents: makeParsedAgents(agentNames),
|
||||
mode: 'ref',
|
||||
label,
|
||||
alias,
|
||||
ln,
|
||||
};
|
||||
},
|
||||
|
||||
defineAgents: (agentNames, {ln = 0} = {}) => {
|
||||
return {
|
||||
type: 'agent define',
|
||||
|
@ -116,6 +127,51 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
};
|
||||
},
|
||||
|
||||
blockBegin: (mode, {
|
||||
label = jasmine.anything(),
|
||||
left = jasmine.anything(),
|
||||
right = jasmine.anything(),
|
||||
ln = jasmine.anything(),
|
||||
} = {}) => {
|
||||
return {
|
||||
type: 'block begin',
|
||||
mode,
|
||||
label,
|
||||
left,
|
||||
right,
|
||||
ln,
|
||||
};
|
||||
},
|
||||
|
||||
blockSplit: (mode, {
|
||||
label = jasmine.anything(),
|
||||
left = jasmine.anything(),
|
||||
right = jasmine.anything(),
|
||||
ln = jasmine.anything(),
|
||||
} = {}) => {
|
||||
return {
|
||||
type: 'block split',
|
||||
mode,
|
||||
label,
|
||||
left,
|
||||
right,
|
||||
ln,
|
||||
};
|
||||
},
|
||||
|
||||
blockEnd: ({
|
||||
left = jasmine.anything(),
|
||||
right = jasmine.anything(),
|
||||
ln = jasmine.anything(),
|
||||
} = {}) => {
|
||||
return {
|
||||
type: 'block end',
|
||||
left,
|
||||
right,
|
||||
ln,
|
||||
};
|
||||
},
|
||||
|
||||
connect: (agentNames, {
|
||||
label = jasmine.anything(),
|
||||
line = jasmine.anything(),
|
||||
|
@ -741,39 +797,44 @@ 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(),
|
||||
]});
|
||||
|
||||
const block0 = sequence.stages[0];
|
||||
expect(block0.type).toEqual('block');
|
||||
expect(block0.left).toEqual('__BLOCK0[');
|
||||
expect(block0.right).toEqual('__BLOCK0]');
|
||||
});
|
||||
|
||||
it('records all sections within blocks', () => {
|
||||
it('propagates block statements', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.blockBegin('if', 'abc', {ln: 10}),
|
||||
PARSED.connect(['A', 'B']),
|
||||
PARSED.blockSplit('else', 'xyz', {ln: 20}),
|
||||
PARSED.connect(['A', 'C']),
|
||||
PARSED.connect(['A', 'B']),
|
||||
PARSED.blockEnd({ln: 30}),
|
||||
]});
|
||||
|
||||
expect(sequence.stages).toEqual([
|
||||
GENERATED.blockBegin('if', {label: 'abc', ln: 10}),
|
||||
jasmine.anything(),
|
||||
jasmine.anything(),
|
||||
GENERATED.blockSplit('else', {label: 'xyz', ln: 20}),
|
||||
jasmine.anything(),
|
||||
GENERATED.blockEnd({ln: 30}),
|
||||
jasmine.anything(),
|
||||
]);
|
||||
});
|
||||
|
||||
it('records virtual block agent names in block commands', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.blockBegin('if', 'abc'),
|
||||
PARSED.connect(['A', 'B']),
|
||||
PARSED.blockSplit('else', 'xyz'),
|
||||
PARSED.connect(['A', 'B']),
|
||||
PARSED.blockEnd(),
|
||||
]});
|
||||
|
||||
const block0 = sequence.stages[0];
|
||||
expect(block0.sections).toEqual([
|
||||
{mode: 'if', label: 'abc', ln: 10, stages: [
|
||||
GENERATED.beginAgents(['A', 'B']),
|
||||
GENERATED.connect(['A', 'B']),
|
||||
]},
|
||||
{mode: 'else', label: 'xyz', ln: 20, stages: [
|
||||
GENERATED.beginAgents(['C']),
|
||||
GENERATED.connect(['A', 'C']),
|
||||
]},
|
||||
]);
|
||||
const bounds = {
|
||||
left: '__BLOCK0[',
|
||||
right: '__BLOCK0]',
|
||||
};
|
||||
|
||||
const stages = sequence.stages;
|
||||
expect(stages[0]).toEqual(GENERATED.blockBegin('if', bounds));
|
||||
expect(stages[3]).toEqual(GENERATED.blockSplit('else', bounds));
|
||||
expect(stages[5]).toEqual(GENERATED.blockEnd(bounds));
|
||||
});
|
||||
|
||||
it('records virtual block agents in nested blocks', () => {
|
||||
|
@ -798,15 +859,22 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
{name: '__BLOCK0]', anchorRight: false},
|
||||
{name: ']', anchorRight: false},
|
||||
]);
|
||||
const block0 = sequence.stages[0];
|
||||
expect(block0.type).toEqual('block');
|
||||
expect(block0.left).toEqual('__BLOCK0[');
|
||||
expect(block0.right).toEqual('__BLOCK0]');
|
||||
|
||||
const block1 = block0.sections[1].stages[0];
|
||||
expect(block1.type).toEqual('block');
|
||||
expect(block1.left).toEqual('__BLOCK1[');
|
||||
expect(block1.right).toEqual('__BLOCK1]');
|
||||
const bounds0 = {
|
||||
left: '__BLOCK0[',
|
||||
right: '__BLOCK0]',
|
||||
};
|
||||
|
||||
const bounds1 = {
|
||||
left: '__BLOCK1[',
|
||||
right: '__BLOCK1]',
|
||||
};
|
||||
|
||||
const stages = sequence.stages;
|
||||
expect(stages[0]).toEqual(GENERATED.blockBegin('if', bounds0));
|
||||
expect(stages[4]).toEqual(GENERATED.blockBegin('if', bounds1));
|
||||
expect(stages[7]).toEqual(GENERATED.blockEnd(bounds1));
|
||||
expect(stages[8]).toEqual(GENERATED.blockEnd(bounds0));
|
||||
});
|
||||
|
||||
it('preserves block boundaries when agents exist outside', () => {
|
||||
|
@ -829,123 +897,92 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
{name: '__BLOCK0]', anchorRight: false},
|
||||
{name: ']', anchorRight: false},
|
||||
]);
|
||||
const block0 = sequence.stages[2];
|
||||
expect(block0.type).toEqual('block');
|
||||
expect(block0.left).toEqual('__BLOCK0[');
|
||||
expect(block0.right).toEqual('__BLOCK0]');
|
||||
|
||||
const block1 = block0.sections[0].stages[0];
|
||||
expect(block1.type).toEqual('block');
|
||||
expect(block1.left).toEqual('__BLOCK1[');
|
||||
expect(block1.right).toEqual('__BLOCK1]');
|
||||
const bounds0 = {
|
||||
left: '__BLOCK0[',
|
||||
right: '__BLOCK0]',
|
||||
};
|
||||
|
||||
const bounds1 = {
|
||||
left: '__BLOCK1[',
|
||||
right: '__BLOCK1]',
|
||||
};
|
||||
|
||||
const stages = sequence.stages;
|
||||
expect(stages[2]).toEqual(GENERATED.blockBegin('if', bounds0));
|
||||
expect(stages[3]).toEqual(GENERATED.blockBegin('if', bounds1));
|
||||
expect(stages[5]).toEqual(GENERATED.blockEnd(bounds1));
|
||||
expect(stages[6]).toEqual(GENERATED.blockEnd(bounds0));
|
||||
});
|
||||
|
||||
it('allows empty block parts after split', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
expect(() => generator.generate({stages: [
|
||||
PARSED.blockBegin('if', 'abc'),
|
||||
PARSED.connect(['A', 'B']),
|
||||
PARSED.blockSplit('else', 'xyz'),
|
||||
PARSED.blockEnd(),
|
||||
]});
|
||||
|
||||
const block0 = sequence.stages[0];
|
||||
expect(block0.sections).toEqual([
|
||||
{mode: 'if', label: 'abc', ln: 0, stages: [
|
||||
jasmine.anything(),
|
||||
jasmine.anything(),
|
||||
]},
|
||||
{mode: 'else', label: 'xyz', ln: 0, stages: []},
|
||||
]);
|
||||
]})).not.toThrow();
|
||||
});
|
||||
|
||||
it('allows empty block parts before split', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
expect(() => generator.generate({stages: [
|
||||
PARSED.blockBegin('if', 'abc'),
|
||||
PARSED.blockSplit('else', 'xyz'),
|
||||
PARSED.connect(['A', 'B']),
|
||||
PARSED.blockEnd(),
|
||||
]});
|
||||
|
||||
const block0 = sequence.stages[0];
|
||||
expect(block0.sections).toEqual([
|
||||
{mode: 'if', label: 'abc', ln: 0, stages: []},
|
||||
{mode: 'else', label: 'xyz', ln: 0, stages: [
|
||||
jasmine.anything(),
|
||||
jasmine.anything(),
|
||||
]},
|
||||
]);
|
||||
]})).not.toThrow();
|
||||
});
|
||||
|
||||
it('removes entirely empty blocks', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
it('allows deeply nested blocks', () => {
|
||||
expect(() => generator.generate({stages: [
|
||||
PARSED.blockBegin('if', 'abc'),
|
||||
PARSED.blockBegin('if', 'def'),
|
||||
PARSED.connect(['A', 'B']),
|
||||
PARSED.blockEnd(),
|
||||
PARSED.blockEnd(),
|
||||
]})).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects entirely empty blocks', () => {
|
||||
expect(() => generator.generate({stages: [
|
||||
PARSED.blockBegin('if', 'abc'),
|
||||
PARSED.blockSplit('else', 'xyz'),
|
||||
PARSED.blockBegin('if', 'abc'),
|
||||
PARSED.blockEnd(),
|
||||
PARSED.blockEnd(),
|
||||
]});
|
||||
|
||||
expect(sequence.stages).toEqual([]);
|
||||
]})).toThrow();
|
||||
});
|
||||
|
||||
it('removes blocks containing only define statements / markers', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
it('rejects blocks containing only define statements / markers', () => {
|
||||
expect(() => generator.generate({stages: [
|
||||
PARSED.blockBegin('if', 'abc'),
|
||||
PARSED.defineAgents(['A']),
|
||||
{type: 'mark', name: 'foo'},
|
||||
PARSED.blockEnd(),
|
||||
]});
|
||||
|
||||
expect(sequence.stages).toEqual([]);
|
||||
]})).toThrow();
|
||||
});
|
||||
|
||||
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(),
|
||||
]});
|
||||
|
||||
expect(sequence.agents).toEqual([
|
||||
{name: '[', anchorRight: true},
|
||||
{name: ']', anchorRight: false},
|
||||
]);
|
||||
});
|
||||
|
||||
it('removes entirely empty nested blocks', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
it('rejects entirely empty nested blocks', () => {
|
||||
expect(() => generator.generate({stages: [
|
||||
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];
|
||||
expect(block0.sections).toEqual([
|
||||
{mode: 'if', label: 'abc', ln: 0, stages: [
|
||||
jasmine.anything(),
|
||||
jasmine.anything(),
|
||||
]},
|
||||
{mode: 'else', label: 'xyz', ln: 0, stages: []},
|
||||
]);
|
||||
]})).toThrow();
|
||||
});
|
||||
|
||||
it('rejects unterminated blocks', () => {
|
||||
expect(() => generator.generate({stages: [
|
||||
PARSED.blockBegin('if', 'abc'),
|
||||
PARSED.connect(['A', 'B']),
|
||||
]})).toThrow();
|
||||
]})).toThrow(new Error('Unterminated section at line 1'));
|
||||
|
||||
expect(() => generator.generate({stages: [
|
||||
PARSED.blockBegin('if', 'abc'),
|
||||
PARSED.blockBegin('if', 'def'),
|
||||
PARSED.connect(['A', 'B']),
|
||||
PARSED.blockEnd(),
|
||||
]})).toThrow();
|
||||
]})).toThrow(new Error('Unterminated section at line 1'));
|
||||
});
|
||||
|
||||
it('rejects extra block terminations', () => {
|
||||
|
|
|
@ -255,6 +255,9 @@ define([
|
|||
}
|
||||
|
||||
const type = tokenKeyword(line[1]);
|
||||
if(!type) {
|
||||
throw makeError('Unspecified termination', line[0]);
|
||||
}
|
||||
if(TERMINATOR_TYPES.indexOf(type) === -1) {
|
||||
throw makeError('Unknown termination "' + type + '"', line[1]);
|
||||
}
|
||||
|
@ -268,6 +271,9 @@ define([
|
|||
}
|
||||
|
||||
const type = tokenKeyword(line[1]);
|
||||
if(!type) {
|
||||
throw makeError('Unspecified header', line[0]);
|
||||
}
|
||||
if(TERMINATOR_TYPES.indexOf(type) === -1) {
|
||||
throw makeError('Unknown header "' + type + '"', line[1]);
|
||||
}
|
||||
|
@ -313,6 +319,38 @@ define([
|
|||
};
|
||||
},
|
||||
|
||||
(line) => { // begin reference
|
||||
if(
|
||||
tokenKeyword(line[0]) !== 'begin' ||
|
||||
tokenKeyword(line[1]) !== 'reference'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
let agents = [];
|
||||
const labelSep = findToken(line, ':');
|
||||
if(tokenKeyword(line[2]) === 'over' && labelSep > 3) {
|
||||
agents = readAgentList(line, 3, labelSep);
|
||||
} else if(labelSep !== 2) {
|
||||
throw makeError('Expected ":" or "over"', line[2]);
|
||||
}
|
||||
const def = readAgent(
|
||||
line,
|
||||
labelSep + 1,
|
||||
line.length,
|
||||
{aliases: true}
|
||||
);
|
||||
if(!def.alias) {
|
||||
throw makeError('Reference must have an alias', line[labelSep]);
|
||||
}
|
||||
return {
|
||||
type: 'group begin',
|
||||
agents,
|
||||
mode: 'ref',
|
||||
label: def.name,
|
||||
alias: def.alias,
|
||||
};
|
||||
},
|
||||
|
||||
(line) => { // agent
|
||||
const type = AGENT_MANIPULATION_TYPES[tokenKeyword(line[0])];
|
||||
if(!type || line.length <= 1) {
|
||||
|
|
|
@ -425,6 +425,34 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('converts reference commands', () => {
|
||||
const parsed = parser.parse(
|
||||
'begin reference: Foo bar as baz\n' +
|
||||
'begin reference over A, B: Foo bar as baz\n'
|
||||
);
|
||||
expect(parsed.stages).toEqual([
|
||||
{
|
||||
type: 'group begin',
|
||||
ln: jasmine.anything(),
|
||||
agents: [],
|
||||
mode: 'ref',
|
||||
label: 'Foo bar',
|
||||
alias: 'baz',
|
||||
},
|
||||
{
|
||||
type: 'group begin',
|
||||
ln: jasmine.anything(),
|
||||
agents: [
|
||||
{name: 'A', alias: '', flags: []},
|
||||
{name: 'B', alias: '', flags: []},
|
||||
],
|
||||
mode: 'ref',
|
||||
label: 'Foo bar',
|
||||
alias: 'baz',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('converts markers', () => {
|
||||
const parsed = parser.parse('abc:');
|
||||
expect(parsed.stages).toEqual([{
|
||||
|
@ -530,12 +558,24 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
|||
));
|
||||
});
|
||||
|
||||
it('rejects missing terminators', () => {
|
||||
expect(() => parser.parse('terminators')).toThrow(new Error(
|
||||
'Unspecified termination at line 1, character 0'
|
||||
));
|
||||
});
|
||||
|
||||
it('rejects invalid headers', () => {
|
||||
expect(() => parser.parse('headers foo')).toThrow(new Error(
|
||||
'Unknown header "foo" at line 1, character 8'
|
||||
));
|
||||
});
|
||||
|
||||
it('rejects missing headers', () => {
|
||||
expect(() => parser.parse('headers')).toThrow(new Error(
|
||||
'Unspecified header at line 1, character 0'
|
||||
));
|
||||
});
|
||||
|
||||
it('rejects malformed notes', () => {
|
||||
expect(() => parser.parse('note over A hello')).toThrow();
|
||||
});
|
||||
|
|
|
@ -5,6 +5,8 @@ define([
|
|||
'svg/SVGUtilities',
|
||||
'svg/SVGShapes',
|
||||
'./components/BaseComponent',
|
||||
'./components/Block',
|
||||
'./components/Parallel',
|
||||
'./components/Marker',
|
||||
'./components/AgentCap',
|
||||
'./components/AgentHighlight',
|
||||
|
@ -20,35 +22,6 @@ define([
|
|||
/* jshint +W072 */
|
||||
'use strict';
|
||||
|
||||
function traverse(stages, callbacks) {
|
||||
stages.forEach((stage) => {
|
||||
if(stage.type === 'block') {
|
||||
const scope = {};
|
||||
if(callbacks.blockBeginFn) {
|
||||
callbacks.blockBeginFn(scope, stage);
|
||||
}
|
||||
stage.sections.forEach((section) => {
|
||||
if(callbacks.sectionBeginFn) {
|
||||
callbacks.sectionBeginFn(scope, stage, section);
|
||||
}
|
||||
traverse(section.stages, callbacks);
|
||||
if(callbacks.sectionEndFn) {
|
||||
callbacks.sectionEndFn(scope, stage, section);
|
||||
}
|
||||
});
|
||||
if(callbacks.blockEndFn) {
|
||||
callbacks.blockEndFn(scope, stage);
|
||||
}
|
||||
} else if(callbacks.stagesFn) {
|
||||
if(stage.type === 'parallel') {
|
||||
callbacks.stagesFn(stage.stages);
|
||||
} else {
|
||||
callbacks.stagesFn([stage]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function findExtremes(agentInfos, agentNames) {
|
||||
let min = null;
|
||||
let max = null;
|
||||
|
@ -102,20 +75,8 @@ define([
|
|||
components = BaseComponent.getComponents();
|
||||
}
|
||||
|
||||
this.separationTraversalFns = {
|
||||
stagesFn: this.separationStages.bind(this),
|
||||
blockBeginFn: this.separationBlockBegin.bind(this),
|
||||
sectionBeginFn: this.separationSectionBegin.bind(this),
|
||||
blockEndFn: this.separationBlockEnd.bind(this),
|
||||
};
|
||||
|
||||
this.renderTraversalFns = {
|
||||
stagesFn: this.renderStages.bind(this),
|
||||
blockBeginFn: this.renderBlockBegin.bind(this),
|
||||
sectionBeginFn: this.renderSectionBegin.bind(this),
|
||||
sectionEndFn: this.renderSectionEnd.bind(this),
|
||||
blockEndFn: this.renderBlockEnd.bind(this),
|
||||
};
|
||||
this.separationStage = this.separationStage.bind(this);
|
||||
this.renderStage = this.renderStage.bind(this);
|
||||
|
||||
this.addSeparation = this.addSeparation.bind(this);
|
||||
this.addDef = this.addDef.bind(this);
|
||||
|
@ -196,28 +157,7 @@ define([
|
|||
info2.separations.set(agentName1, Math.max(d2, dist));
|
||||
}
|
||||
|
||||
separationBlockBegin(scope, {left, right}) {
|
||||
array.mergeSets(this.visibleAgents, [left, right]);
|
||||
}
|
||||
|
||||
separationSectionBegin(scope, {left, right}, {mode, label}) {
|
||||
const config = this.theme.block.section;
|
||||
const width = (
|
||||
this.sizer.measure(config.mode.labelAttrs, mode).width +
|
||||
config.mode.padding.left +
|
||||
config.mode.padding.right +
|
||||
this.sizer.measure(config.label.labelAttrs, label).width +
|
||||
config.label.padding.left +
|
||||
config.label.padding.right
|
||||
);
|
||||
this.addSeparation(left, right, width);
|
||||
}
|
||||
|
||||
separationBlockEnd(scope, {left, right}) {
|
||||
array.removeAll(this.visibleAgents, [left, right]);
|
||||
}
|
||||
|
||||
separationStages(stages) {
|
||||
separationStage(stage) {
|
||||
const agentSpaces = new Map();
|
||||
const agentNames = this.visibleAgents.slice();
|
||||
|
||||
|
@ -239,13 +179,14 @@ define([
|
|||
textSizer: this.sizer,
|
||||
addSpacing,
|
||||
addSeparation: this.addSeparation,
|
||||
components: this.components,
|
||||
};
|
||||
stages.forEach((stage) => {
|
||||
this.components.get(stage.type).separationPre(stage, env);
|
||||
});
|
||||
stages.forEach((stage) => {
|
||||
this.components.get(stage.type).separation(stage, env);
|
||||
});
|
||||
const component = this.components.get(stage.type);
|
||||
if(!component) {
|
||||
throw new Error('Unknown component: ' + stage.type);
|
||||
}
|
||||
component.separationPre(stage, env);
|
||||
component.separation(stage, env);
|
||||
array.mergeSets(agentNames, this.visibleAgents);
|
||||
|
||||
agentNames.forEach((agentNameR) => {
|
||||
|
@ -327,87 +268,6 @@ define([
|
|||
}
|
||||
}
|
||||
|
||||
renderBlockBegin(scope, {left, right}) {
|
||||
this.currentY = (
|
||||
this.checkAgentRange([left, right], this.currentY) +
|
||||
this.theme.block.margin.top
|
||||
);
|
||||
|
||||
scope.y = this.currentY;
|
||||
scope.first = true;
|
||||
this.markAgentRange([left, right], this.currentY);
|
||||
}
|
||||
|
||||
renderSectionBegin(scope, {left, right}, {mode, label}) {
|
||||
this.currentY = this.checkAgentRange([left, right], this.currentY);
|
||||
const config = this.theme.block;
|
||||
const agentInfoL = this.agentInfos.get(left);
|
||||
const agentInfoR = this.agentInfos.get(right);
|
||||
|
||||
if(scope.first) {
|
||||
scope.first = false;
|
||||
} else {
|
||||
this.currentY += config.section.padding.bottom;
|
||||
this.sections.appendChild(svg.make('line', Object.assign({
|
||||
'x1': agentInfoL.x,
|
||||
'y1': this.currentY,
|
||||
'x2': agentInfoR.x,
|
||||
'y2': this.currentY,
|
||||
}, config.separator.attrs)));
|
||||
}
|
||||
|
||||
const modeRender = SVGShapes.renderBoxedText(mode, {
|
||||
x: agentInfoL.x,
|
||||
y: this.currentY,
|
||||
padding: config.section.mode.padding,
|
||||
boxAttrs: config.section.mode.boxAttrs,
|
||||
labelAttrs: config.section.mode.labelAttrs,
|
||||
boxLayer: this.blocks,
|
||||
labelLayer: this.actionLabels,
|
||||
SVGTextBlockClass: this.SVGTextBlockClass,
|
||||
});
|
||||
|
||||
const labelRender = SVGShapes.renderBoxedText(label, {
|
||||
x: agentInfoL.x + modeRender.width,
|
||||
y: this.currentY,
|
||||
padding: config.section.label.padding,
|
||||
boxAttrs: {'fill': '#000000'},
|
||||
labelAttrs: config.section.label.labelAttrs,
|
||||
boxLayer: this.mask,
|
||||
labelLayer: this.actionLabels,
|
||||
SVGTextBlockClass: this.SVGTextBlockClass,
|
||||
});
|
||||
|
||||
this.currentY += (
|
||||
Math.max(modeRender.height, labelRender.height) +
|
||||
config.section.padding.top
|
||||
);
|
||||
this.markAgentRange([left, right], this.currentY);
|
||||
}
|
||||
|
||||
renderSectionEnd(/*scope, block, section*/) {
|
||||
}
|
||||
|
||||
renderBlockEnd(scope, {left, right}) {
|
||||
const config = this.theme.block;
|
||||
this.currentY = (
|
||||
this.checkAgentRange([left, right], this.currentY) +
|
||||
config.section.padding.bottom
|
||||
);
|
||||
|
||||
const agentInfoL = this.agentInfos.get(left);
|
||||
const agentInfoR = this.agentInfos.get(right);
|
||||
this.blocks.appendChild(svg.make('rect', Object.assign({
|
||||
'x': agentInfoL.x,
|
||||
'y': scope.y,
|
||||
'width': agentInfoR.x - agentInfoL.x,
|
||||
'height': this.currentY - scope.y,
|
||||
}, config.boxAttrs)));
|
||||
|
||||
this.currentY += config.margin.bottom + this.theme.actionMargin;
|
||||
this.markAgentRange([left, right], this.currentY);
|
||||
}
|
||||
|
||||
addHighlightObject(line, o) {
|
||||
let list = this.highlights.get(line);
|
||||
if(!list) {
|
||||
|
@ -417,44 +277,53 @@ define([
|
|||
list.push(o);
|
||||
}
|
||||
|
||||
renderStages(stages) {
|
||||
renderStage(stage) {
|
||||
this.agentInfos.forEach((agentInfo) => {
|
||||
const rad = agentInfo.currentRad;
|
||||
agentInfo.currentMaxRad = rad;
|
||||
});
|
||||
|
||||
let topY = 0;
|
||||
let maxTopShift = 0;
|
||||
let sequential = true;
|
||||
const envPre = {
|
||||
theme: this.theme,
|
||||
agentInfos: this.agentInfos,
|
||||
textSizer: this.sizer,
|
||||
state: this.state,
|
||||
components: this.components,
|
||||
};
|
||||
const touchedAgentNames = [];
|
||||
stages.forEach((stage) => {
|
||||
const component = this.components.get(stage.type);
|
||||
const r = component.renderPre(stage, envPre) || {};
|
||||
if(r.topShift !== undefined) {
|
||||
maxTopShift = Math.max(maxTopShift, r.topShift);
|
||||
}
|
||||
if(r.agentNames) {
|
||||
array.mergeSets(touchedAgentNames, r.agentNames);
|
||||
}
|
||||
if(r.asynchronousY !== undefined) {
|
||||
topY = Math.max(topY, r.asynchronousY);
|
||||
sequential = false;
|
||||
const result = component.renderPre(stage, envPre);
|
||||
const {topShift, agentNames, asynchronousY} =
|
||||
BaseComponent.cleanRenderPreResult(result, this.currentY);
|
||||
|
||||
const topY = this.checkAgentRange(agentNames, asynchronousY);
|
||||
|
||||
const eventOut = () => {
|
||||
this.trigger('mouseout');
|
||||
};
|
||||
|
||||
const makeRegion = (o, stageOverride = null) => {
|
||||
if(!o) {
|
||||
o = svg.make('g');
|
||||
}
|
||||
const targetStage = (stageOverride || stage);
|
||||
this.addHighlightObject(targetStage.ln, o);
|
||||
o.setAttribute('class', 'region');
|
||||
o.addEventListener('mouseenter', () => {
|
||||
this.trigger('mouseover', [targetStage]);
|
||||
});
|
||||
topY = this.checkAgentRange(touchedAgentNames, topY);
|
||||
if(sequential) {
|
||||
topY = Math.max(topY, this.currentY);
|
||||
}
|
||||
o.addEventListener('mouseleave', eventOut);
|
||||
o.addEventListener('click', () => {
|
||||
this.trigger('click', [targetStage]);
|
||||
});
|
||||
this.actionLabels.appendChild(o);
|
||||
return o;
|
||||
};
|
||||
|
||||
const env = {
|
||||
topY,
|
||||
primaryY: topY + maxTopShift,
|
||||
primaryY: topY + topShift,
|
||||
blockLayer: this.blocks,
|
||||
sectionLayer: this.sections,
|
||||
shapeLayer: this.actionShapes,
|
||||
labelLayer: this.actionLabels,
|
||||
maskLayer: this.mask,
|
||||
|
@ -469,41 +338,12 @@ define([
|
|||
agentInfo.latestYStart = andStop ? null : toY;
|
||||
},
|
||||
addDef: this.addDef,
|
||||
};
|
||||
let bottomY = topY;
|
||||
stages.forEach((stage) => {
|
||||
const eventOver = () => {
|
||||
this.trigger('mouseover', [stage]);
|
||||
makeRegion,
|
||||
components: this.components,
|
||||
};
|
||||
|
||||
const eventOut = () => {
|
||||
this.trigger('mouseout');
|
||||
};
|
||||
|
||||
const eventClick = () => {
|
||||
this.trigger('click', [stage]);
|
||||
};
|
||||
|
||||
env.makeRegion = (o) => {
|
||||
if(!o) {
|
||||
o = svg.make('g');
|
||||
}
|
||||
this.addHighlightObject(stage.ln, o);
|
||||
o.setAttribute('class', 'region');
|
||||
o.addEventListener('mouseenter', eventOver);
|
||||
o.addEventListener('mouseleave', eventOut);
|
||||
o.addEventListener('click', eventClick);
|
||||
this.actionLabels.appendChild(o);
|
||||
return o;
|
||||
};
|
||||
|
||||
const component = this.components.get(stage.type);
|
||||
const baseY = component.render(stage, env);
|
||||
if(baseY !== undefined) {
|
||||
bottomY = Math.max(bottomY, baseY);
|
||||
}
|
||||
});
|
||||
this.markAgentRange(touchedAgentNames, bottomY);
|
||||
const bottomY = Math.max(topY, component.render(stage, env) || 0);
|
||||
this.markAgentRange(agentNames, bottomY);
|
||||
|
||||
this.currentY = bottomY;
|
||||
}
|
||||
|
@ -564,7 +404,7 @@ define([
|
|||
});
|
||||
|
||||
this.visibleAgents = ['[', ']'];
|
||||
traverse(stages, this.separationTraversalFns);
|
||||
stages.forEach(this.separationStage);
|
||||
|
||||
this.positionAgents();
|
||||
}
|
||||
|
@ -654,7 +494,7 @@ define([
|
|||
this.buildAgentInfos(sequence.agents, sequence.stages);
|
||||
|
||||
this.currentY = 0;
|
||||
traverse(sequence.stages, this.renderTraversalFns);
|
||||
sequence.stages.forEach(this.renderStage);
|
||||
const bottomY = this.checkAgentRange(['[', ']'], this.currentY);
|
||||
|
||||
const stagesHeight = Math.max(bottomY - this.theme.actionMargin, 0);
|
||||
|
|
|
@ -16,6 +16,7 @@ define(() => {
|
|||
textSizer,
|
||||
addSpacing,
|
||||
addSeparation,
|
||||
components,
|
||||
}*/) {
|
||||
}
|
||||
|
||||
|
@ -26,6 +27,7 @@ define(() => {
|
|||
textSizer,
|
||||
addSpacing,
|
||||
addSeparation,
|
||||
components,
|
||||
}*/) {
|
||||
}
|
||||
|
||||
|
@ -34,12 +36,16 @@ define(() => {
|
|||
agentInfos,
|
||||
textSizer,
|
||||
state,
|
||||
components,
|
||||
}*/) {
|
||||
// return {topShift, agentNames, asynchronousY}
|
||||
}
|
||||
|
||||
render(/*stage, {
|
||||
topY,
|
||||
primaryY,
|
||||
blockLayer,
|
||||
sectionLayer,
|
||||
shapeLayer,
|
||||
labelLayer,
|
||||
theme,
|
||||
|
@ -49,10 +55,24 @@ define(() => {
|
|||
addDef,
|
||||
makeRegion,
|
||||
state,
|
||||
components,
|
||||
}*/) {
|
||||
// return bottom Y coordinate
|
||||
}
|
||||
}
|
||||
|
||||
BaseComponent.cleanRenderPreResult = ({
|
||||
topShift = 0,
|
||||
agentNames = [],
|
||||
asynchronousY = null,
|
||||
} = {}, currentY = null) => {
|
||||
return {
|
||||
topShift,
|
||||
agentNames,
|
||||
asynchronousY: (asynchronousY !== null) ? asynchronousY : currentY,
|
||||
};
|
||||
};
|
||||
|
||||
const components = new Map();
|
||||
|
||||
BaseComponent.register = (name, component) => {
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
define([
|
||||
'./BaseComponent',
|
||||
'core/ArrayUtilities',
|
||||
'svg/SVGUtilities',
|
||||
'svg/SVGShapes',
|
||||
], (
|
||||
BaseComponent,
|
||||
array,
|
||||
svg,
|
||||
SVGShapes
|
||||
) => {
|
||||
'use strict';
|
||||
|
||||
class BlockSplit extends BaseComponent {
|
||||
separation({left, right, mode, label}, env) {
|
||||
const config = env.theme.block.section;
|
||||
const width = (
|
||||
env.textSizer.measure(config.mode.labelAttrs, mode).width +
|
||||
config.mode.padding.left +
|
||||
config.mode.padding.right +
|
||||
env.textSizer.measure(config.label.labelAttrs, label).width +
|
||||
config.label.padding.left +
|
||||
config.label.padding.right
|
||||
);
|
||||
env.addSeparation(left, right, width);
|
||||
}
|
||||
|
||||
renderPre({left, right}) {
|
||||
return {
|
||||
agentNames: [left, right],
|
||||
};
|
||||
}
|
||||
|
||||
render({left, right, mode, label}, env, first = false) {
|
||||
const config = env.theme.block;
|
||||
const agentInfoL = env.agentInfos.get(left);
|
||||
const agentInfoR = env.agentInfos.get(right);
|
||||
|
||||
let y = env.primaryY;
|
||||
|
||||
if(!first) {
|
||||
y += config.section.padding.bottom;
|
||||
env.sectionLayer.appendChild(svg.make('line', Object.assign({
|
||||
'x1': agentInfoL.x,
|
||||
'y1': y,
|
||||
'x2': agentInfoR.x,
|
||||
'y2': y,
|
||||
}, config.separator.attrs)));
|
||||
}
|
||||
|
||||
const modeRender = SVGShapes.renderBoxedText(mode, {
|
||||
x: agentInfoL.x,
|
||||
y,
|
||||
padding: config.section.mode.padding,
|
||||
boxAttrs: config.section.mode.boxAttrs,
|
||||
labelAttrs: config.section.mode.labelAttrs,
|
||||
boxLayer: env.blockLayer,
|
||||
labelLayer: env.labelLayer,
|
||||
SVGTextBlockClass: env.SVGTextBlockClass,
|
||||
});
|
||||
|
||||
const labelRender = SVGShapes.renderBoxedText(label, {
|
||||
x: agentInfoL.x + modeRender.width,
|
||||
y,
|
||||
padding: config.section.label.padding,
|
||||
boxAttrs: {'fill': '#000000'},
|
||||
labelAttrs: config.section.label.labelAttrs,
|
||||
boxLayer: env.maskLayer,
|
||||
labelLayer: env.labelLayer,
|
||||
SVGTextBlockClass: env.SVGTextBlockClass,
|
||||
});
|
||||
|
||||
return y + (
|
||||
Math.max(modeRender.height, labelRender.height) +
|
||||
config.section.padding.top
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BlockBegin extends BlockSplit {
|
||||
makeState(state) {
|
||||
state.blocks = new Map();
|
||||
}
|
||||
|
||||
resetState(state) {
|
||||
state.blocks.clear();
|
||||
}
|
||||
|
||||
separation(stage, env) {
|
||||
array.mergeSets(env.visibleAgents, [stage.left, stage.right]);
|
||||
super.separation(stage, env);
|
||||
}
|
||||
|
||||
renderPre({left, right}, env) {
|
||||
return {
|
||||
agentNames: [left, right],
|
||||
topShift: env.theme.block.margin.top,
|
||||
};
|
||||
}
|
||||
|
||||
render(stage, env) {
|
||||
env.state.blocks.set(stage.left, env.primaryY);
|
||||
return super.render(stage, env, true);
|
||||
}
|
||||
}
|
||||
|
||||
class BlockEnd extends BaseComponent {
|
||||
separation({left, right}, env) {
|
||||
array.removeAll(env.visibleAgents, [left, right]);
|
||||
}
|
||||
|
||||
renderPre({left, right}, env) {
|
||||
return {
|
||||
agentNames: [left, right],
|
||||
topShift: env.theme.block.section.padding.bottom,
|
||||
};
|
||||
}
|
||||
|
||||
render({left, right}, env) {
|
||||
const config = env.theme.block;
|
||||
|
||||
const startY = env.state.blocks.get(left);
|
||||
|
||||
const agentInfoL = env.agentInfos.get(left);
|
||||
const agentInfoR = env.agentInfos.get(right);
|
||||
env.blockLayer.appendChild(svg.make('rect', Object.assign({
|
||||
'x': agentInfoL.x,
|
||||
'y': startY,
|
||||
'width': agentInfoR.x - agentInfoL.x,
|
||||
'height': env.primaryY - startY,
|
||||
}, config.boxAttrs)));
|
||||
|
||||
return env.primaryY + config.margin.bottom + env.theme.actionMargin;
|
||||
}
|
||||
}
|
||||
|
||||
BaseComponent.register('block begin', new BlockBegin());
|
||||
BaseComponent.register('block split', new BlockSplit());
|
||||
BaseComponent.register('block end', new BlockEnd());
|
||||
|
||||
return {
|
||||
BlockBegin,
|
||||
BlockSplit,
|
||||
BlockEnd,
|
||||
};
|
||||
});
|
|
@ -0,0 +1,22 @@
|
|||
defineDescribe('Block', [
|
||||
'./Block',
|
||||
'./BaseComponent',
|
||||
], (
|
||||
Block,
|
||||
BaseComponent
|
||||
) => {
|
||||
'use strict';
|
||||
|
||||
it('registers itself with the component store', () => {
|
||||
const components = BaseComponent.getComponents();
|
||||
expect(components.get('block begin')).toEqual(
|
||||
jasmine.any(Block.BlockBegin)
|
||||
);
|
||||
expect(components.get('block split')).toEqual(
|
||||
jasmine.any(Block.BlockSplit)
|
||||
);
|
||||
expect(components.get('block end')).toEqual(
|
||||
jasmine.any(Block.BlockEnd)
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,76 @@
|
|||
define([
|
||||
'./BaseComponent',
|
||||
'core/ArrayUtilities',
|
||||
], (
|
||||
BaseComponent,
|
||||
array
|
||||
) => {
|
||||
'use strict';
|
||||
|
||||
function nullableMax(a = null, b = null) {
|
||||
if(a === null) {
|
||||
return b;
|
||||
}
|
||||
if(b === null) {
|
||||
return a;
|
||||
}
|
||||
return Math.max(a, b);
|
||||
}
|
||||
|
||||
function mergeResults(a, b) {
|
||||
array.mergeSets(a.agentNames, b.agentNames);
|
||||
return {
|
||||
topShift: Math.max(a.topShift, b.topShift),
|
||||
agentNames: a.agentNames,
|
||||
asynchronousY: nullableMax(a.asynchronousY, b.asynchronousY),
|
||||
};
|
||||
}
|
||||
|
||||
class Parallel extends BaseComponent {
|
||||
separationPre(stage, env) {
|
||||
stage.stages.forEach((subStage) => {
|
||||
env.components.get(subStage.type).separationPre(subStage, env);
|
||||
});
|
||||
}
|
||||
|
||||
separation(stage, env) {
|
||||
stage.stages.forEach((subStage) => {
|
||||
env.components.get(subStage.type).separation(subStage, env);
|
||||
});
|
||||
}
|
||||
|
||||
renderPre(stage, env) {
|
||||
const baseResults = {
|
||||
topShift: 0,
|
||||
agentNames: [],
|
||||
asynchronousY: null,
|
||||
};
|
||||
|
||||
return stage.stages.map((subStage) => {
|
||||
const component = env.components.get(subStage.type);
|
||||
const subResult = component.renderPre(subStage, env);
|
||||
return BaseComponent.cleanRenderPreResult(subResult);
|
||||
}).reduce(mergeResults, baseResults);
|
||||
}
|
||||
|
||||
render(stage, env) {
|
||||
const originalMakeRegion = env.makeRegion;
|
||||
let bottomY = 0;
|
||||
stage.stages.forEach((subStage) => {
|
||||
env.makeRegion = (o, stageOverride = null) => {
|
||||
return originalMakeRegion(o, stageOverride || subStage);
|
||||
};
|
||||
|
||||
const component = env.components.get(subStage.type);
|
||||
const baseY = component.render(subStage, env) || 0;
|
||||
bottomY = Math.max(bottomY, baseY);
|
||||
});
|
||||
env.makeRegion = originalMakeRegion;
|
||||
return bottomY;
|
||||
}
|
||||
}
|
||||
|
||||
BaseComponent.register('parallel', new Parallel());
|
||||
|
||||
return Parallel;
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
defineDescribe('Parallel', [
|
||||
'./Parallel',
|
||||
'./BaseComponent',
|
||||
], (
|
||||
Parallel,
|
||||
BaseComponent
|
||||
) => {
|
||||
'use strict';
|
||||
|
||||
it('registers itself with the component store', () => {
|
||||
const components = BaseComponent.getComponents();
|
||||
expect(components.get('parallel')).toEqual(jasmine.any(Parallel));
|
||||
});
|
||||
});
|
|
@ -14,8 +14,10 @@ define([
|
|||
'sequence/themes/Chunky_spec',
|
||||
'sequence/components/AgentCap_spec',
|
||||
'sequence/components/AgentHighlight_spec',
|
||||
'sequence/components/Block_spec',
|
||||
'sequence/components/Connect_spec',
|
||||
'sequence/components/Marker_spec',
|
||||
'sequence/components/Note_spec',
|
||||
'sequence/components/Parallel_spec',
|
||||
'sequence/sequence_integration_spec',
|
||||
]);
|
||||
|
|
Loading…
Reference in New Issue