Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/@types/execution.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export type IParsedElementInstruction = {
/** Type definition of the parsed element entry returned on fetching next element. */
export type IParsedElement = IParsedElementArgument | IParsedElementInstruction;

export type IPCRoutineCall = {
type: '__callroutine__';
nodeID: string;
};

/* Type definition for program counter override signals. */
export type TPCOverride =
| '__rollback__'
Expand All @@ -36,4 +41,5 @@ export type TPCOverride =
| '__goinnerlast__'
| '__goup__'
| '__repeat__'
| IPCRoutineCall
| null;
17 changes: 16 additions & 1 deletion src/execution/interpreter/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { addGlobalSymbol, getGlobalSymbol } from '../scope';
import { setPCOverride, clearPCOverride, setExecutionItem, getNextElement } from '../parser';
import {
setPCOverride,
clearPCOverride,
setExecutionItem,
getNextElement,
invokeRoutineByNodeID,
} from '../parser';

import {
ElementData,
Expand Down Expand Up @@ -60,6 +66,15 @@ export function releaseProgramCounter(): void {
clearPCOverride();
}

/**
* Invokes a routine by its node ID for the current execution item.
* @param routineNodeID syntax tree node ID of the routine
* @returns `true` if routine exists, else `false`
*/
export function invokeRoutine(routineNodeID: string): boolean {
return invokeRoutineByNodeID(routineNodeID);
}

/**
* Runs a process, routine, or crumb stack from start to end.
* @param nodeID syntax tree node ID of the starting node
Expand Down
258 changes: 258 additions & 0 deletions src/execution/parser/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
setPCOverride,
clearPCOverride,
stackTrace,
invokeRoutineByNodeID,
} from '.';

import {
Expand Down Expand Up @@ -1074,6 +1075,263 @@ describe('Parser', () => {
});
});

describe('routine call', () => {
test('single routine call', () => {
resetSyntaxTree();
generateFromSnapshot({
process: [
{
elementName: 'process',
argMap: null,
scope: [
{
elementName: 'box-boolean',
argMap: {
name: { elementName: 'value-string' },
value: { elementName: 'value-boolean' },
},
},
{
elementName: 'box-number',
argMap: {
name: { elementName: 'value-string' },
value: { elementName: 'value-number' },
},
},
],
},
],
routine: [
{
elementName: 'routine',
argMap: { name: { elementName: 'value-string' } },
scope: [
{
elementName: 'box-string',
argMap: {
name: { elementName: 'value-string' },
value: { elementName: 'value-string' },
},
},
],
},
],
crumbs: [],
});

const processNode = getProcessNodes()[0];
const routineNode = getRoutineNodes()[0];

setExecutionItem(processNode.nodeID);

const results: string[] = [];
let guard = 0;
while (guard < 100) {
const next = getNextElement();
if (next === null) break;
results.push(next.instance.name);

if (next.instance.name === 'box-boolean') {
invokeRoutineByNodeID(routineNode.nodeID);
}
guard++;
}

expect(results).toEqual([
'process',
'value-string',
'value-boolean',
'box-boolean',
'value-string',
'routine',
'value-string',
'value-string',
'box-string',
'routine',
'value-string',
'value-number',
'box-number',
'process',
]);
});

test('nested routine call', () => {
resetSyntaxTree();
generateFromSnapshot({
process: [
{
elementName: 'process',
argMap: null,
scope: [
{
elementName: 'box-boolean',
argMap: {
name: { elementName: 'value-string' },
value: { elementName: 'value-boolean' },
},
},
],
},
],
routine: [
{
// Routine A
elementName: 'routine',
argMap: { name: { elementName: 'value-string' } },
scope: [
{
elementName: 'box-string',
argMap: {
name: { elementName: 'value-string' },
value: { elementName: 'value-string' },
},
},
],
},
{
// Routine B
elementName: 'routine',
argMap: { name: { elementName: 'value-string' } },
scope: [
{
elementName: 'box-number',
argMap: {
name: { elementName: 'value-string' },
value: { elementName: 'value-number' },
},
},
],
},
],
crumbs: [],
});

const processNode = getProcessNodes()[0];
const routineNodes = getRoutineNodes();
const routineA = routineNodes[0];
const routineB = routineNodes[1];

setExecutionItem(processNode.nodeID);

const results: string[] = [];
let guard = 0;
while (guard < 100) {
const next = getNextElement();
if (next === null) break;
results.push(next.instance.name);

if (next.instance.name === 'box-boolean') {
invokeRoutineByNodeID(routineA.nodeID);
} else if (next.instance.name === 'box-string') {
invokeRoutineByNodeID(routineB.nodeID);
}
guard++;
}

expect(results).toEqual([
'process',
'value-string',
'value-boolean',
'box-boolean',
'value-string',
'routine',
'value-string',
'value-string',
'box-string',
'value-string',
'routine',
'value-string',
'value-number',
'box-number',
'routine',
'routine',
'process',
]);
});

test('multi process isolation', () => {
resetSyntaxTree();
generateFromSnapshot({
process: [
{
elementName: 'process',
argMap: null,
scope: [
{
elementName: 'box-boolean',
argMap: {
name: { elementName: 'value-string' },
value: { elementName: 'value-boolean' },
},
},
],
},
{
elementName: 'process',
argMap: null,
scope: [
{
elementName: 'box-number',
argMap: {
name: { elementName: 'value-string' },
value: { elementName: 'value-number' },
},
},
],
},
],
routine: [
{
elementName: 'routine',
argMap: { name: { elementName: 'value-string' } },
scope: [
{
elementName: 'box-string',
argMap: {
name: { elementName: 'value-string' },
value: { elementName: 'value-string' },
},
},
],
},
],
crumbs: [],
});

const processNodes = getProcessNodes();
const routineNode = getRoutineNodes()[0];

// Execute Process 1
setExecutionItem(processNodes[0].nodeID);
const results1: string[] = [];
let guard = 0;
while (guard < 100) {
const next = getNextElement();
if (next === null) break;
results1.push(next.instance.name);
if (next.instance.name === 'box-boolean') {
invokeRoutineByNodeID(routineNode.nodeID);
}
guard++;
}

// Execute Process 2 (Reset should have happened)
setExecutionItem(processNodes[1].nodeID);
const results2: string[] = [];
guard = 0;
while (guard < 100) {
const next = getNextElement();
if (next === null) break;
results2.push(next.instance.name);
guard++;
}

expect(results1).toContain('box-string');
expect(results2).not.toContain('box-string');
expect(results2).toContain('box-number');
});
});

describe('execution call frame stack trace', () => {
test('verify stack trace', () => {
resetSyntaxTree();
Expand Down
Loading