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;
}
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 = [
'agent begin',
'agent end',
@ -1884,7 +1900,8 @@
return (
checkAgentConflicts(allStages) ||
checkReferenceConflicts(allStages) ||
checkDelayedConflicts(allStages)
checkDelayedConflicts(allStages) ||
checkActivationConflicts(allStages)
);
}
@ -1949,6 +1966,7 @@
this.currentNest = null;
this.stageHandlers = {
'agent activation': this.handleAgentActivation.bind(this),
'agent begin': this.handleAgentBegin.bind(this),
'agent define': this.handleAgentDefine.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}) {
const gAgents = agents.map(this.toGAgent);
this.validateGAgents(gAgents);
@ -3326,7 +3352,9 @@
];
const PARALLEL_TASKS = [
'activate',
'begin',
'deactivate',
'end',
'note',
'state',
@ -3635,6 +3663,8 @@
'as': CM_ERROR,
'\n': end,
}},
'activate': {type: 'keyword', then: {'': agentListTo({'\n': end})}},
'deactivate': {type: 'keyword', then: {'': agentListTo({'\n': end})}},
'if': commonGroup,
'else': {type: 'keyword', suggest: ['else\n', 'else 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('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) {
let suffix = '';
if(token) {
@ -4957,6 +4997,16 @@
}, 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
if(tokenKeyword(last(line)) !== ':') {
return null;

File diff suppressed because one or more lines are too long

View File

@ -1854,6 +1854,22 @@
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 = [
'agent begin',
'agent end',
@ -1884,7 +1900,8 @@
return (
checkAgentConflicts(allStages) ||
checkReferenceConflicts(allStages) ||
checkDelayedConflicts(allStages)
checkDelayedConflicts(allStages) ||
checkActivationConflicts(allStages)
);
}
@ -1949,6 +1966,7 @@
this.currentNest = null;
this.stageHandlers = {
'agent activation': this.handleAgentActivation.bind(this),
'agent begin': this.handleAgentBegin.bind(this),
'agent define': this.handleAgentDefine.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}) {
const gAgents = agents.map(this.toGAgent);
this.validateGAgents(gAgents);
@ -3326,7 +3352,9 @@
];
const PARALLEL_TASKS = [
'activate',
'begin',
'deactivate',
'end',
'note',
'state',
@ -3635,6 +3663,8 @@
'as': CM_ERROR,
'\n': end,
}},
'activate': {type: 'keyword', then: {'': agentListTo({'\n': end})}},
'deactivate': {type: 'keyword', then: {'': agentListTo({'\n': end})}},
'if': commonGroup,
'else': {type: 'keyword', suggest: ['else\n', 'else 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('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) {
let suffix = '';
if(token) {
@ -4957,6 +4997,16 @@
}, 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
if(tokenKeyword(last(line)) !== ':') {
return null;

View File

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

View File

@ -270,6 +270,22 @@ function checkDelayedConflicts(allStages) {
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 = [
'agent begin',
'agent end',
@ -300,7 +316,8 @@ function errorForParallel(existing, latest) {
return (
checkAgentConflicts(allStages) ||
checkReferenceConflicts(allStages) ||
checkDelayedConflicts(allStages)
checkDelayedConflicts(allStages) ||
checkActivationConflicts(allStages)
);
}
@ -365,6 +382,7 @@ export default class Generator {
this.currentNest = null;
this.stageHandlers = {
'agent activation': this.handleAgentActivation.bind(this),
'agent begin': this.handleAgentBegin.bind(this),
'agent define': this.handleAgentDefine.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}) {
const gAgents = agents.map(this.toGAgent);
this.validateGAgents(gAgents);

View File

@ -25,6 +25,17 @@ describe('Sequence Generator', () => {
const [PARSED_SOURCE] = makeParsedAgents([{flags: ['source']}]);
const PARSED = {
agentActivation: (agents, activated, {
ln = 0,
parallel = false,
} = {}) => ({
activated,
agents: makeParsedAgents(agents),
ln,
parallel,
type: 'agent activation',
}),
agentBegin: (agents, {
ln = 0,
mode = 'box',
@ -227,15 +238,6 @@ describe('Sequence Generator', () => {
};
const GENERATED = {
activation: (agentIDs, activated, {
ln = any(),
} = {}) => ({
activated,
agentIDs,
ln,
type: 'agent activation',
}),
agent: (id, {
anchorRight = any(),
formattedLabel = any(),
@ -249,6 +251,15 @@ describe('Sequence Generator', () => {
options,
}),
agentActivation: (agentIDs, activated, {
ln = any(),
} = {}) => ({
activated,
agentIDs,
ln,
type: 'agent activation',
}),
agentBegin: (agentIDs, {
mode = 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', () => {
const sequence = invoke([
PARSED.connect([
@ -871,12 +944,12 @@ describe('Sequence Generator', () => {
expect(sequence.stages).toEqual([
any(),
GENERATED.parallel([
GENERATED.activation(['A'], true),
GENERATED.agentActivation(['A'], true),
GENERATED.connectBegin(['A', 'A'], {label: 'woo!'}),
]),
GENERATED.parallel([
GENERATED.connectEnd(),
GENERATED.activation(['A'], false),
GENERATED.agentActivation(['A'], false),
]),
any(),
]);
@ -1194,12 +1267,12 @@ describe('Sequence Generator', () => {
expect(sequence.stages).toEqual([
any(),
GENERATED.parallel([
GENERATED.activation(['B'], true),
GENERATED.agentActivation(['B'], true),
GENERATED.connect(['A', 'B']),
]),
GENERATED.parallel([
GENERATED.connect(['A', 'B']),
GENERATED.activation(['B'], false),
GENERATED.agentActivation(['B'], false),
]),
any(),
]);
@ -1246,7 +1319,7 @@ describe('Sequence Generator', () => {
any(),
GENERATED.parallel([
GENERATED.connect(['A', 'B']),
GENERATED.activation(['B'], false),
GENERATED.agentActivation(['B'], false),
GENERATED.agentEnd(['B']),
]),
GENERATED.agentEnd(['A']),
@ -1285,7 +1358,7 @@ describe('Sequence Generator', () => {
any(),
any(),
GENERATED.parallel([
GENERATED.activation(['B'], false),
GENERATED.agentActivation(['B'], false),
GENERATED.agentEnd(['A', 'B']),
]),
]);
@ -1301,7 +1374,7 @@ describe('Sequence Generator', () => {
any(),
any(),
GENERATED.parallel([
GENERATED.activation(['B'], false),
GENERATED.agentActivation(['B'], false),
GENERATED.agentEnd(['A', 'B']),
]),
]);
@ -1321,7 +1394,7 @@ describe('Sequence Generator', () => {
any(),
any(),
GENERATED.parallel([
GENERATED.activation(['A', 'B'], false),
GENERATED.agentActivation(['A', 'B'], false),
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('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) {
let suffix = '';
if(token) {
@ -450,6 +460,16 @@ const PARSERS = [
}, 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
if(tokenKeyword(last(line)) !== ':') {
return null;

View File

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