Add activate/deactivate syntax [#26]

This commit is contained in:
David Evans 2018-05-12 22:28:27 +01:00
parent 827a94d712
commit 4cdb4ad584
8 changed files with 260 additions and 21 deletions

View File

@ -1854,6 +1854,22 @@
return null; return null;
} }
function checkActivationConflicts(allStages) {
const seen = new Set();
for(const stage of allStages) {
if(stage.type !== 'agent activation') {
continue;
}
for(const agentID of stage.agentIDs) {
if(seen.has(agentID)) {
return 'Conflicting agent activation';
}
seen.add(agentID);
}
}
return null;
}
const PARALLEL_STAGES = [ const PARALLEL_STAGES = [
'agent begin', 'agent begin',
'agent end', 'agent end',
@ -1884,7 +1900,8 @@
return ( return (
checkAgentConflicts(allStages) || checkAgentConflicts(allStages) ||
checkReferenceConflicts(allStages) || checkReferenceConflicts(allStages) ||
checkDelayedConflicts(allStages) checkDelayedConflicts(allStages) ||
checkActivationConflicts(allStages)
); );
} }
@ -1949,6 +1966,7 @@
this.currentNest = null; this.currentNest = null;
this.stageHandlers = { this.stageHandlers = {
'agent activation': this.handleAgentActivation.bind(this),
'agent begin': this.handleAgentBegin.bind(this), 'agent begin': this.handleAgentBegin.bind(this),
'agent define': this.handleAgentDefine.bind(this), 'agent define': this.handleAgentDefine.bind(this),
'agent end': this.handleAgentEnd.bind(this), 'agent end': this.handleAgentEnd.bind(this),
@ -2777,6 +2795,14 @@
}); });
} }
handleAgentActivation({agents, activated, parallel}) {
const gAgents = agents.map(this.toGAgent);
this.validateGAgents(gAgents);
this.defineGAgents(gAgents);
this.addImpStage(this.setGAgentVis(gAgents, true, 'box'), {parallel});
this.addStage(this.setGAgentActivation(gAgents, activated), {parallel});
}
handleAgentBegin({agents, mode, parallel}) { handleAgentBegin({agents, mode, parallel}) {
const gAgents = agents.map(this.toGAgent); const gAgents = agents.map(this.toGAgent);
this.validateGAgents(gAgents); this.validateGAgents(gAgents);
@ -3326,7 +3352,9 @@
]; ];
const PARALLEL_TASKS = [ const PARALLEL_TASKS = [
'activate',
'begin', 'begin',
'deactivate',
'end', 'end',
'note', 'note',
'state', 'state',
@ -3635,6 +3663,8 @@
'as': CM_ERROR, 'as': CM_ERROR,
'\n': end, '\n': end,
}}, }},
'activate': {type: 'keyword', then: {'': agentListTo({'\n': end})}},
'deactivate': {type: 'keyword', then: {'': agentListTo({'\n': end})}},
'if': commonGroup, 'if': commonGroup,
'else': {type: 'keyword', suggest: ['else\n', 'else if: '], then: { 'else': {type: 'keyword', suggest: ['else\n', 'else if: '], then: {
'if': {type: 'keyword', suggest: ['if: '], then: { 'if': {type: 'keyword', suggest: ['if: '], then: {
@ -4661,6 +4691,16 @@
AGENT_MANIPULATION_TYPES.set('begin', {mode: 'box', type: 'agent begin'}); AGENT_MANIPULATION_TYPES.set('begin', {mode: 'box', type: 'agent begin'});
AGENT_MANIPULATION_TYPES.set('end', {mode: 'cross', type: 'agent end'}); AGENT_MANIPULATION_TYPES.set('end', {mode: 'cross', type: 'agent end'});
const AGENT_ACTIVATION_TYPES = new Map();
AGENT_MANIPULATION_TYPES.set('activate', {
activated: true,
type: 'agent activation',
});
AGENT_MANIPULATION_TYPES.set('deactivate', {
activated: false,
type: 'agent activation',
});
function makeError(message, token = null) { function makeError(message, token = null) {
let suffix = ''; let suffix = '';
if(token) { if(token) {
@ -4957,6 +4997,16 @@
}, type); }, type);
}}, }},
{begin: [], fn: (line) => { // Activation
const type = AGENT_ACTIVATION_TYPES.get(tokenKeyword(line[0]));
if(!type || line.length <= 1) {
return null;
}
return Object.assign({
agents: readAgentList(line, 1, line.length, {aliases: false}),
}, type);
}},
{begin: ['simultaneously'], fn: (line) => { // Async {begin: ['simultaneously'], fn: (line) => { // Async
if(tokenKeyword(last(line)) !== ':') { if(tokenKeyword(last(line)) !== ':') {
return null; return null;

File diff suppressed because one or more lines are too long

View File

@ -1854,6 +1854,22 @@
return null; return null;
} }
function checkActivationConflicts(allStages) {
const seen = new Set();
for(const stage of allStages) {
if(stage.type !== 'agent activation') {
continue;
}
for(const agentID of stage.agentIDs) {
if(seen.has(agentID)) {
return 'Conflicting agent activation';
}
seen.add(agentID);
}
}
return null;
}
const PARALLEL_STAGES = [ const PARALLEL_STAGES = [
'agent begin', 'agent begin',
'agent end', 'agent end',
@ -1884,7 +1900,8 @@
return ( return (
checkAgentConflicts(allStages) || checkAgentConflicts(allStages) ||
checkReferenceConflicts(allStages) || checkReferenceConflicts(allStages) ||
checkDelayedConflicts(allStages) checkDelayedConflicts(allStages) ||
checkActivationConflicts(allStages)
); );
} }
@ -1949,6 +1966,7 @@
this.currentNest = null; this.currentNest = null;
this.stageHandlers = { this.stageHandlers = {
'agent activation': this.handleAgentActivation.bind(this),
'agent begin': this.handleAgentBegin.bind(this), 'agent begin': this.handleAgentBegin.bind(this),
'agent define': this.handleAgentDefine.bind(this), 'agent define': this.handleAgentDefine.bind(this),
'agent end': this.handleAgentEnd.bind(this), 'agent end': this.handleAgentEnd.bind(this),
@ -2777,6 +2795,14 @@
}); });
} }
handleAgentActivation({agents, activated, parallel}) {
const gAgents = agents.map(this.toGAgent);
this.validateGAgents(gAgents);
this.defineGAgents(gAgents);
this.addImpStage(this.setGAgentVis(gAgents, true, 'box'), {parallel});
this.addStage(this.setGAgentActivation(gAgents, activated), {parallel});
}
handleAgentBegin({agents, mode, parallel}) { handleAgentBegin({agents, mode, parallel}) {
const gAgents = agents.map(this.toGAgent); const gAgents = agents.map(this.toGAgent);
this.validateGAgents(gAgents); this.validateGAgents(gAgents);
@ -3326,7 +3352,9 @@
]; ];
const PARALLEL_TASKS = [ const PARALLEL_TASKS = [
'activate',
'begin', 'begin',
'deactivate',
'end', 'end',
'note', 'note',
'state', 'state',
@ -3635,6 +3663,8 @@
'as': CM_ERROR, 'as': CM_ERROR,
'\n': end, '\n': end,
}}, }},
'activate': {type: 'keyword', then: {'': agentListTo({'\n': end})}},
'deactivate': {type: 'keyword', then: {'': agentListTo({'\n': end})}},
'if': commonGroup, 'if': commonGroup,
'else': {type: 'keyword', suggest: ['else\n', 'else if: '], then: { 'else': {type: 'keyword', suggest: ['else\n', 'else if: '], then: {
'if': {type: 'keyword', suggest: ['if: '], then: { 'if': {type: 'keyword', suggest: ['if: '], then: {
@ -4661,6 +4691,16 @@
AGENT_MANIPULATION_TYPES.set('begin', {mode: 'box', type: 'agent begin'}); AGENT_MANIPULATION_TYPES.set('begin', {mode: 'box', type: 'agent begin'});
AGENT_MANIPULATION_TYPES.set('end', {mode: 'cross', type: 'agent end'}); AGENT_MANIPULATION_TYPES.set('end', {mode: 'cross', type: 'agent end'});
const AGENT_ACTIVATION_TYPES = new Map();
AGENT_MANIPULATION_TYPES.set('activate', {
activated: true,
type: 'agent activation',
});
AGENT_MANIPULATION_TYPES.set('deactivate', {
activated: false,
type: 'agent activation',
});
function makeError(message, token = null) { function makeError(message, token = null) {
let suffix = ''; let suffix = '';
if(token) { if(token) {
@ -4957,6 +4997,16 @@
}, type); }, type);
}}, }},
{begin: [], fn: (line) => { // Activation
const type = AGENT_ACTIVATION_TYPES.get(tokenKeyword(line[0]));
if(!type || line.length <= 1) {
return null;
}
return Object.assign({
agents: readAgentList(line, 1, line.length, {aliases: false}),
}, type);
}},
{begin: ['simultaneously'], fn: (line) => { // Async {begin: ['simultaneously'], fn: (line) => { // Async
if(tokenKeyword(last(line)) !== ':') { if(tokenKeyword(last(line)) !== ':') {
return null; return null;

View File

@ -36,7 +36,9 @@ const AGENT_INFO_TYPES = [
]; ];
const PARALLEL_TASKS = [ const PARALLEL_TASKS = [
'activate',
'begin', 'begin',
'deactivate',
'end', 'end',
'note', 'note',
'state', 'state',
@ -345,6 +347,8 @@ const makeCommands = ((() => {
'as': CM_ERROR, 'as': CM_ERROR,
'\n': end, '\n': end,
}}, }},
'activate': {type: 'keyword', then: {'': agentListTo({'\n': end})}},
'deactivate': {type: 'keyword', then: {'': agentListTo({'\n': end})}},
'if': commonGroup, 'if': commonGroup,
'else': {type: 'keyword', suggest: ['else\n', 'else if: '], then: { 'else': {type: 'keyword', suggest: ['else\n', 'else if: '], then: {
'if': {type: 'keyword', suggest: ['if: '], then: { 'if': {type: 'keyword', suggest: ['if: '], then: {

View File

@ -270,6 +270,22 @@ function checkDelayedConflicts(allStages) {
return null; return null;
} }
function checkActivationConflicts(allStages) {
const seen = new Set();
for(const stage of allStages) {
if(stage.type !== 'agent activation') {
continue;
}
for(const agentID of stage.agentIDs) {
if(seen.has(agentID)) {
return 'Conflicting agent activation';
}
seen.add(agentID);
}
}
return null;
}
const PARALLEL_STAGES = [ const PARALLEL_STAGES = [
'agent begin', 'agent begin',
'agent end', 'agent end',
@ -300,7 +316,8 @@ function errorForParallel(existing, latest) {
return ( return (
checkAgentConflicts(allStages) || checkAgentConflicts(allStages) ||
checkReferenceConflicts(allStages) || checkReferenceConflicts(allStages) ||
checkDelayedConflicts(allStages) checkDelayedConflicts(allStages) ||
checkActivationConflicts(allStages)
); );
} }
@ -365,6 +382,7 @@ export default class Generator {
this.currentNest = null; this.currentNest = null;
this.stageHandlers = { this.stageHandlers = {
'agent activation': this.handleAgentActivation.bind(this),
'agent begin': this.handleAgentBegin.bind(this), 'agent begin': this.handleAgentBegin.bind(this),
'agent define': this.handleAgentDefine.bind(this), 'agent define': this.handleAgentDefine.bind(this),
'agent end': this.handleAgentEnd.bind(this), 'agent end': this.handleAgentEnd.bind(this),
@ -1193,6 +1211,14 @@ export default class Generator {
}); });
} }
handleAgentActivation({agents, activated, parallel}) {
const gAgents = agents.map(this.toGAgent);
this.validateGAgents(gAgents);
this.defineGAgents(gAgents);
this.addImpStage(this.setGAgentVis(gAgents, true, 'box'), {parallel});
this.addStage(this.setGAgentActivation(gAgents, activated), {parallel});
}
handleAgentBegin({agents, mode, parallel}) { handleAgentBegin({agents, mode, parallel}) {
const gAgents = agents.map(this.toGAgent); const gAgents = agents.map(this.toGAgent);
this.validateGAgents(gAgents); this.validateGAgents(gAgents);

View File

@ -25,6 +25,17 @@ describe('Sequence Generator', () => {
const [PARSED_SOURCE] = makeParsedAgents([{flags: ['source']}]); const [PARSED_SOURCE] = makeParsedAgents([{flags: ['source']}]);
const PARSED = { const PARSED = {
agentActivation: (agents, activated, {
ln = 0,
parallel = false,
} = {}) => ({
activated,
agents: makeParsedAgents(agents),
ln,
parallel,
type: 'agent activation',
}),
agentBegin: (agents, { agentBegin: (agents, {
ln = 0, ln = 0,
mode = 'box', mode = 'box',
@ -227,15 +238,6 @@ describe('Sequence Generator', () => {
}; };
const GENERATED = { const GENERATED = {
activation: (agentIDs, activated, {
ln = any(),
} = {}) => ({
activated,
agentIDs,
ln,
type: 'agent activation',
}),
agent: (id, { agent: (id, {
anchorRight = any(), anchorRight = any(),
formattedLabel = any(), formattedLabel = any(),
@ -249,6 +251,15 @@ describe('Sequence Generator', () => {
options, options,
}), }),
agentActivation: (agentIDs, activated, {
ln = any(),
} = {}) => ({
activated,
agentIDs,
ln,
type: 'agent activation',
}),
agentBegin: (agentIDs, { agentBegin: (agentIDs, {
mode = any(), mode = any(),
ln = any(), ln = any(),
@ -860,6 +871,68 @@ describe('Sequence Generator', () => {
]); ]);
}); });
it('propegates activation stages', () => {
const sequence = invoke([
PARSED.agentActivation(['A', 'B'], true),
]);
expect(sequence.stages).toEqual([
any(),
GENERATED.agentActivation(['A', 'B'], true),
any(),
]);
});
it('adds implicit begin stages for activation', () => {
const sequence = invoke([
PARSED.agentActivation(['A', 'B'], true),
]);
expect(sequence.stages).toEqual([
GENERATED.agentBegin(['A', 'B']),
any(),
any(),
]);
});
it('implicitly combines compatible activation stages', () => {
const sequence = invoke([
PARSED.agentBegin(['A', 'B']),
PARSED.agentActivation(['A'], true),
PARSED.agentActivation(['B'], true),
]);
expect(sequence.stages).toEqual([
any(),
GENERATED.agentActivation(['A', 'B'], true),
any(),
]);
});
it('maintains separation of incompatible activation stages', () => {
const sequence = invoke([
PARSED.agentBegin(['A']),
PARSED.agentActivation(['A'], true),
PARSED.agentActivation(['A'], false),
]);
expect(sequence.stages).toEqual([
any(),
GENERATED.agentActivation(['A'], true),
GENERATED.agentActivation(['A'], false),
any(),
]);
});
it('rejects conflicting parallel activation stages', () => {
expect(() => invoke([
PARSED.agentActivation(['A'], true),
PARSED.agentActivation(['A'], false, {parallel: true}),
])).toThrow(new Error(
'Conflicting agent activation at line 1'
));
});
it('adds parallel activation stages to self connections', () => { it('adds parallel activation stages to self connections', () => {
const sequence = invoke([ const sequence = invoke([
PARSED.connect([ PARSED.connect([
@ -871,12 +944,12 @@ describe('Sequence Generator', () => {
expect(sequence.stages).toEqual([ expect(sequence.stages).toEqual([
any(), any(),
GENERATED.parallel([ GENERATED.parallel([
GENERATED.activation(['A'], true), GENERATED.agentActivation(['A'], true),
GENERATED.connectBegin(['A', 'A'], {label: 'woo!'}), GENERATED.connectBegin(['A', 'A'], {label: 'woo!'}),
]), ]),
GENERATED.parallel([ GENERATED.parallel([
GENERATED.connectEnd(), GENERATED.connectEnd(),
GENERATED.activation(['A'], false), GENERATED.agentActivation(['A'], false),
]), ]),
any(), any(),
]); ]);
@ -1194,12 +1267,12 @@ describe('Sequence Generator', () => {
expect(sequence.stages).toEqual([ expect(sequence.stages).toEqual([
any(), any(),
GENERATED.parallel([ GENERATED.parallel([
GENERATED.activation(['B'], true), GENERATED.agentActivation(['B'], true),
GENERATED.connect(['A', 'B']), GENERATED.connect(['A', 'B']),
]), ]),
GENERATED.parallel([ GENERATED.parallel([
GENERATED.connect(['A', 'B']), GENERATED.connect(['A', 'B']),
GENERATED.activation(['B'], false), GENERATED.agentActivation(['B'], false),
]), ]),
any(), any(),
]); ]);
@ -1246,7 +1319,7 @@ describe('Sequence Generator', () => {
any(), any(),
GENERATED.parallel([ GENERATED.parallel([
GENERATED.connect(['A', 'B']), GENERATED.connect(['A', 'B']),
GENERATED.activation(['B'], false), GENERATED.agentActivation(['B'], false),
GENERATED.agentEnd(['B']), GENERATED.agentEnd(['B']),
]), ]),
GENERATED.agentEnd(['A']), GENERATED.agentEnd(['A']),
@ -1285,7 +1358,7 @@ describe('Sequence Generator', () => {
any(), any(),
any(), any(),
GENERATED.parallel([ GENERATED.parallel([
GENERATED.activation(['B'], false), GENERATED.agentActivation(['B'], false),
GENERATED.agentEnd(['A', 'B']), GENERATED.agentEnd(['A', 'B']),
]), ]),
]); ]);
@ -1301,7 +1374,7 @@ describe('Sequence Generator', () => {
any(), any(),
any(), any(),
GENERATED.parallel([ GENERATED.parallel([
GENERATED.activation(['B'], false), GENERATED.agentActivation(['B'], false),
GENERATED.agentEnd(['A', 'B']), GENERATED.agentEnd(['A', 'B']),
]), ]),
]); ]);
@ -1321,7 +1394,7 @@ describe('Sequence Generator', () => {
any(), any(),
any(), any(),
GENERATED.parallel([ GENERATED.parallel([
GENERATED.activation(['A', 'B'], false), GENERATED.agentActivation(['A', 'B'], false),
GENERATED.agentEnd(['A', 'B']), GENERATED.agentEnd(['A', 'B']),
]), ]),
]); ]);

View File

@ -154,6 +154,16 @@ AGENT_MANIPULATION_TYPES.set('define', {type: 'agent define'});
AGENT_MANIPULATION_TYPES.set('begin', {mode: 'box', type: 'agent begin'}); AGENT_MANIPULATION_TYPES.set('begin', {mode: 'box', type: 'agent begin'});
AGENT_MANIPULATION_TYPES.set('end', {mode: 'cross', type: 'agent end'}); AGENT_MANIPULATION_TYPES.set('end', {mode: 'cross', type: 'agent end'});
const AGENT_ACTIVATION_TYPES = new Map();
AGENT_MANIPULATION_TYPES.set('activate', {
activated: true,
type: 'agent activation',
});
AGENT_MANIPULATION_TYPES.set('deactivate', {
activated: false,
type: 'agent activation',
});
function makeError(message, token = null) { function makeError(message, token = null) {
let suffix = ''; let suffix = '';
if(token) { if(token) {
@ -450,6 +460,16 @@ const PARSERS = [
}, type); }, type);
}}, }},
{begin: [], fn: (line) => { // Activation
const type = AGENT_ACTIVATION_TYPES.get(tokenKeyword(line[0]));
if(!type || line.length <= 1) {
return null;
}
return Object.assign({
agents: readAgentList(line, 1, line.length, {aliases: false}),
}, type);
}},
{begin: ['simultaneously'], fn: (line) => { // Async {begin: ['simultaneously'], fn: (line) => { // Async
if(tokenKeyword(last(line)) !== ':') { if(tokenKeyword(last(line)) !== ':') {
return null; return null;

View File

@ -19,6 +19,18 @@ describe('Sequence Parser', () => {
const any = () => jasmine.anything(); const any = () => jasmine.anything();
const PARSED = { const PARSED = {
agentActivation: (agents, {
ln = any(),
activated = any(),
parallel = false,
} = {}) => ({
activated,
agents: makeParsedAgents(agents),
ln,
parallel,
type: 'agent activation',
}),
agentBegin: (agents, { agentBegin: (agents, {
ln = any(), ln = any(),
mode = any(), mode = any(),
@ -689,12 +701,16 @@ describe('Sequence Parser', () => {
const parsed = parser.parse( const parsed = parser.parse(
'define A, B\n' + 'define A, B\n' +
'begin A, B\n' + 'begin A, B\n' +
'activate A, B\n' +
'deactivate A, B\n' +
'end A, B\n' 'end A, B\n'
); );
expect(parsed.stages).toEqual([ expect(parsed.stages).toEqual([
PARSED.agentDefine(['A', 'B']), PARSED.agentDefine(['A', 'B']),
PARSED.agentBegin(['A', 'B'], {mode: 'box'}), PARSED.agentBegin(['A', 'B'], {mode: 'box'}),
PARSED.agentActivation(['A', 'B'], {activated: true}),
PARSED.agentActivation(['A', 'B'], {activated: false}),
PARSED.agentEnd(['A', 'B'], {mode: 'cross'}), PARSED.agentEnd(['A', 'B'], {mode: 'cross'}),
]); ]);
}); });