Skip to content

Commit

Permalink
Merge e3722ae into f2ee5de
Browse files Browse the repository at this point in the history
  • Loading branch information
TimSPb89 committed Mar 19, 2018
2 parents f2ee5de + e3722ae commit b902f69
Show file tree
Hide file tree
Showing 18 changed files with 2,601 additions and 92 deletions.
38 changes: 38 additions & 0 deletions src/model/Declaration.js
@@ -0,0 +1,38 @@
import Port from './Port';
import { extractExpression, extractType } from '../parser/WDL/utils/utils';

/**
* Class representing binding points of workflow steps.
*/
export default class Declaration extends Port {
/**
* Create a Declaration.
*
* @param {string} name - Declaration name
* @param {ast} declaration - Declaration ast tree node
* @param {Step} step - Parent step this Declaration belongs to.
*/
constructor(name, declaration, step) {
if (!name) {
throw new Error('Declaration must have a name');
}
if (!declaration.expression || !declaration.type) {
throw new Error('Declaration could be created only using declaration object');
}
super(name, step, {});

delete this.desc;

/**
* Declaration expression extracted object.
* @type {string}
*/
this.expression = extractExpression(declaration.expression);
/**
* Declaration type.
* @type {string}
*/
this.type = extractType(declaration.type);
}

}
59 changes: 59 additions & 0 deletions src/model/Group.js
Expand Up @@ -34,6 +34,65 @@ class Group extends Step {
super(name, new Action(name, config), config);

this.type = type || 'default';
/**
* Dictionary of Group's own declarations.
* @type {Object.<string, Declaration>}
*/
this.ownDeclarations = {};
}

/**
* Add a child step.
*
* @param {Declaration} declaration - Declaration to add.
* @returns {Declaration} Added declaration. May be used for easy declaration creation.
* @throws {Error} Generic exception if a declaration with the same name already exists.
* @example
* const declaration = parent.addDeclaration(new Declaration('declaration', ...));
*/
addDeclaration(declaration) {
const root = this.workflow();
const existingInRoot = root && root.declarations[declaration.name];
const ownExisting = this.ownDeclarations[declaration.name];
if (!existingInRoot && !ownExisting) {
if (declaration.step !== null) {
declaration.step.removeDeclaration(declaration.name, root);
}

declaration.step = this;

if (root) {
root.declarations[declaration.name] = declaration;
}
this.ownDeclarations[declaration.name] = declaration;
} else if (existingInRoot !== declaration && ownExisting !== declaration) {
throw new Error(`Cannot add a declaration with the same name ${declaration.name}.`);
}
return declaration;
}

/**
* Remove a declaration.
*
* @param {string} name - Declaration to remove.
* @param {Workflow} root - Declaration's workflow
* @example
* parent.removeDeclaration('declaration');
*/
removeDeclaration(name, root = null) {
const workflow = root || this.workflow();
const declarationInRoot = workflow && workflow.declarations[name];
const declaration = this.ownDeclarations[name];
if (declarationInRoot) {
declarationInRoot.step = null;
delete workflow.declarations[name];
}
if (declaration) {
declaration.step = null;
delete this.ownDeclarations[name];
}

return declaration;
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/model/Step.js
Expand Up @@ -95,9 +95,9 @@ export default class Step {
* Create a workflow step. You should {@link Workflow#add add} it to a workflow or a compound step.
*
* @param {string} name - Step name. Must be unique in a parent step (e.g. {@link Workflow}).
* @param {Action=} [action={}] action - Action to execute during the step. If no action is specified it will
* @param {Action|{}} action - Action to execute during the step. If no action is specified it will
* automatically be created based on the configuration. Multiple steps may share a single action.
* @param {object} [config={}] - Action configuration containing input bindings.
* @param {object} config - Action configuration containing input bindings.
* It should include action description in case the action is missing.
*
*/
Expand Down
6 changes: 6 additions & 0 deletions src/model/Workflow.js
Expand Up @@ -35,6 +35,12 @@ class Workflow extends Group {
* @type {Object.<string, Action>}
*/
this.actions = {};
/**
* Dictionary of all declarations accessible within workflow.
* @type {Object.<string, Declaration>}
*/
this.declarations = {};

if (config.ast) {
this.ast = config.ast;
}
Expand Down
125 changes: 95 additions & 30 deletions src/parser/WDL/entities/WDLWorkflow.js
Expand Up @@ -3,6 +3,8 @@ import _ from 'lodash';
import Workflow from '../../../model/Workflow';
import Step from '../../../model/Step';
import Group from '../../../model/Group';
import Declaration from '../../../model/Declaration';
import Port from '../../../model/Port';
import { extractExpression, extractType, extractMetaBlock, WDLParserError } from '../utils/utils';
import * as Constants from '../constants';

Expand Down Expand Up @@ -46,19 +48,13 @@ export default class WDLWorkflow {
* @param {list} bodyList - list of the current parsing wdl body node
* @param {string} name - current body name
* @param {Step} parent - parent step
* @param {list} opts - nodes that prohibited for current stage to parse (in lower case)
* @param {[list]} opts - nodes that prohibited for current stage to parse (in lower case)
*/
parseBody(bodyList, name, parent, opts = []) {
const parentStep = parent || this.workflowStep;
let declarationsPassed = false;
bodyList.forEach((item) => {
const lcName = item.name.toLowerCase();
if (_.indexOf(opts, lcName) < 0) {
if (lcName !== 'declaration') {
declarationsPassed = true;
} else if (declarationsPassed) {
throw new WDLParserError('Declarations are allowed only before other things of current scope');
}
this.parsingProcessors[lcName].call(this, item, parentStep);
} else {
throw new WDLParserError(`In ${name} body keys [${opts}] are not allowed`);
Expand Down Expand Up @@ -89,7 +85,8 @@ export default class WDLWorkflow {

const collection = extractExpression(item.attributes.collection);

const port = WDLWorkflow.getPortForBinding(this.workflowStep, parent, collection);
// in scatter the item is always an identifier, so it'll always be one port returned from .getPortsForBinding()
const port = WDLWorkflow.getPortsForBinding(this.workflowStep, parent, collection).shift();

opts.i[itemName] = {};
opts.i[itemName].type = 'ScatterItem';
Expand Down Expand Up @@ -190,8 +187,17 @@ export default class WDLWorkflow {
const declaration = node.attributes.key.source_string;
const expression = extractExpression(nodeValue);

if (!step.i[declaration] && step instanceof Workflow && step.ownDeclarations[declaration]) {
// remove declaration and add it as an input
const declarationObj = step.removeDeclaration(declaration);
const port = new Port(declaration, step, { type: declarationObj.type });
_.forEach(declarationObj.outputs, conn => conn.to.bind(port));
step.i[declaration] = port;
}

if (step.i[declaration]) {
step.i[declaration].bind(WDLWorkflow.getPortForBinding(this.workflowStep, parentStep, expression));
const bindings = WDLWorkflow.getPortsForBinding(this.workflowStep, parentStep, expression);
_.forEach(bindings, binding => step.i[declaration].bind(binding));
} else {
throw new WDLParserError(`Undeclared variable trying to be assigned: call '${step.name}' --> '${declaration}'`);
}
Expand All @@ -209,19 +215,21 @@ export default class WDLWorkflow {
const name = decl.name.source_string;
const type = extractType(decl.type);

const obj = {};
obj[name] = {
type,
};
if (decl.expression === null) { // declaration is an input type
const obj = {};
obj[name] = {
type,
};
parentStep.action.addPorts({
i: obj,
});
} else if (parentStep instanceof Group) { // declaration is a "variable" type
const declaration = new Declaration(name, decl, parentStep);
const bindings = WDLWorkflow.getPortsForBinding(this.workflowStep, declaration.step, declaration.expression);
_.forEach(bindings, binding => declaration.bind(binding));

const str = extractExpression(decl.expression).string;
if (str !== '') {
obj[name].default = str;
parentStep.addDeclaration(declaration);
}

parentStep.action.addPorts({
i: obj,
});
}

/**
Expand Down Expand Up @@ -309,12 +317,7 @@ export default class WDLWorkflow {
});
// WF output connections
if (!wildcard) { // syntax: call_name.output_name
const callOutput = fqn.source_string.split('.');
if (callOutput.length < 2) {
return;
}
const callName = callOutput[0];
const outputName = callOutput[1];
const [callName, outputName] = fqn.source_string.split('.');
const startStep = WDLWorkflow.findStepInStructureRecursively(this.workflowStep, callName);

if (startStep) {
Expand Down Expand Up @@ -378,33 +381,95 @@ export default class WDLWorkflow {
if (_.has(step.i, portName) || _.has(step.o, portName)) {
return step;
}
const root = step.workflow();
if (_.has(root.declarations, portName)) {
return root;
}

return WDLWorkflow.groupNameResolver(step.parent, portName);
}

return undefined;
}

static getPortForBinding(workflow, parent, expression) {
let binder = expression.string;
static getPortsForBinding(workflow, parent, expression) {
const accessesTypes = [
'ArrayOrMapLookup',
'FunctionCall',
'ArrayLiteral',
'ObjectLiteral',
'MapLiteral',
'TupleLiteral',
'LogicalNot',
'UnaryPlus',
'UnaryNegation',
'Add',
'Subtract',
'Multiply',
'Divide',
'Remainder',
'LogicalOr',
'LogicalAnd',
'Equals',
'NotEquals',
'LessThan',
'LessThanOrEqual',
'GreaterThan',
'GreaterThanOrEqual',
'TernaryIf',
'MapLiteralKv',
'ObjectKV',
];
let binder = [expression.string];
if (expression.type === 'MemberAccess') {
const rhsPart = expression.accesses[0].rhs;
const lhsPart = expression.accesses[0].lhs;

const outputStep = WDLWorkflow.findStepInStructureRecursively(workflow, lhsPart);
if (outputStep) {
if (outputStep.o[rhsPart]) {
binder = outputStep.o[rhsPart];
binder = [outputStep.o[rhsPart]];
} else {
throw new WDLParserError(`Undeclared variable is referenced: '${lhsPart}.${rhsPart}'`);
}
} else {
throw new WDLParserError(`Undeclared call is referenced: '${lhsPart}'`);
}
} else if (accessesTypes.includes(expression.type) && expression.accesses.length) {
binder = [];
_.forEach(expression.accesses, (accesses) => {
if (_.isObject(accesses)) {
const outputStep = WDLWorkflow.findStepInStructureRecursively(workflow, accesses.lhs);
if (outputStep) {
if (outputStep.o[accesses.rhs]) {
binder.push(outputStep.o[accesses.rhs]);
} else {
throw new WDLParserError(`Undeclared variable is referenced: '${accesses.lhs}.${accesses.rhs}'`);
}
} else {
throw new WDLParserError(`Undeclared call is referenced: '${accesses.lhs}'`);
}
} else if (_.isString(accesses)) {
const desiredStep = WDLWorkflow.groupNameResolver(parent, accesses);
if (desiredStep) {
if (desiredStep.i[accesses]) {
binder.push(desiredStep.i[accesses]);
} else {
binder.push(desiredStep.declarations[accesses]);
}
} else {
throw new WDLParserError(`Undeclared variable is referenced: '${expression.string}'`);
}
}
});
} else if (expression.type === 'identifier') {
const desiredStep = WDLWorkflow.groupNameResolver(parent, expression.string);
if (desiredStep) {
binder = desiredStep.i[expression.string];
if (desiredStep.i[expression.string]) {
binder = [desiredStep.i[expression.string]];
} else {
binder = [desiredStep.declarations[expression.string]];
}
} else {
throw new WDLParserError(`Undeclared variable is referenced: '${expression.string}'`);
}
Expand Down

0 comments on commit b902f69

Please sign in to comment.