UNPKG

noflo

Version:

Flow-Based Programming environment for JavaScript

414 lines (413 loc) 15 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); // NoFlo - Flow-Based Programming for JavaScript // (c) 2013-2020 Flowhub UG // (c) 2011-2012 Henri Bergius, Nemein // NoFlo may be freely distributed under the MIT license /* eslint-disable no-underscore-dangle */ const debug_1 = require("debug"); const debugComponent = debug_1.default('noflo:component'); class ProcessInput { /** * @param {import("./Ports").InPorts} ports - Component inports * @param {import("./ProcessContext").default} context - Processing context */ constructor(ports, context) { this.ports = ports; this.context = context; this.nodeInstance = this.context.nodeInstance; this.ip = this.context.ip; this.port = this.context.port; this.result = this.context.result; this.scope = this.context.scope; } // When preconditions are met, set component state to `activated` activate() { if (this.context.activated) { return; } if (this.nodeInstance.isOrdered()) { // We're handling packets in order. Set the result as non-resolved // so that it can be send when the order comes up this.result.__resolved = false; } this.nodeInstance.activate(this.context); if (this.port.isAddressable()) { debugComponent(`${this.nodeInstance.nodeId} packet on '${this.port.name}[${this.ip.index}]' caused activation ${this.nodeInstance.load}: ${this.ip.type}`); } else { debugComponent(`${this.nodeInstance.nodeId} packet on '${this.port.name}' caused activation ${this.nodeInstance.load}: ${this.ip.type}`); } } // ## Connection listing // This allows components to check which input ports are attached. This is // useful mainly for addressable ports /** * @param {...string} params - Port names to check for attachment * @returns {Array<number> | Array<Array<number>>} */ attached(...params) { let args = params; if (!args.length) { args = ['in']; } /** @type {Array<Array<number>>} */ const res = []; args.forEach((port) => { if (!this.ports.ports[port]) { throw new Error(`Node ${this.nodeInstance.nodeId} has no port '${port}'`); } res.push(this.ports.ports[port].listAttached()); }); if (args.length === 1) { return res[0]; } return res; } // ## Input preconditions // When the processing function is called, it can check if input buffers // contain the packets needed for the process to fire. // This precondition handling is done via the `has` and `hasStream` methods. // Returns true if a port (or ports joined by logical AND) has a new IP // Passing a validation callback as a last argument allows more selective // checking of packets. /** * @callback HasValidationCallback * @param {IP} ip * @returns {boolean} */ /** * @typedef {string|Array<string|number>} GetArgument */ /** * @typedef {GetArgument|HasValidationCallback} HasArgument */ /** * @param {...HasArgument} params */ has(...params) { let validate; let args = params.filter((p) => typeof p !== 'function'); if (!args.length) { args = ['in']; } if (typeof params[params.length - 1] === 'function') { validate = params[params.length - 1]; } else { validate = () => true; } for (let i = 0; i < args.length; i += 1) { const port = args[i]; if (Array.isArray(port)) { const portImpl = /** @type {import("./InPort").default} */ (this.ports.ports[port[0]]); if (!portImpl) { throw new Error(`Node ${this.nodeInstance.nodeId} has no port '${port[0]}'`); } if (!portImpl.isAddressable()) { throw new Error(`Non-addressable ports, access must be with string ${port[0]}`); } if (!portImpl.has(this.scope, port[1], validate)) { return false; } } else if (typeof port === 'string') { const portImpl = /** @type {import("./InPort").default} */ (this.ports.ports[port]); if (!portImpl) { throw new Error(`Node ${this.nodeInstance.nodeId} has no port '${port}'`); } if (portImpl.isAddressable()) { throw new Error(`For addressable ports, access must be with array [${port}, idx]`); } if (!portImpl.has(this.scope, validate)) { return false; } } else { throw new Error(`Unknown port type ${typeof port}`); } } return true; } // Returns true if the ports contain data packets /** * @param {...string} params - Port names to check for data packets * @returns {boolean} */ hasData(...params) { let args = params; if (!args.length) { args = ['in']; } const hasArgs = [ ...args, /** * @param {import("./IP").default} ip */ (ip) => ip.type === 'data', ]; return this.has(...hasArgs); } // Returns true if a port has a complete stream in its input buffer. /** * @param {...HasArgument} params - Port names to check for streams * @returns {boolean} */ hasStream(...params) { let args = params; /** @type {Function} */ let validateStream; if (!args.length) { args = ['in']; } if (typeof args[args.length - 1] === 'function') { validateStream = /** @type {Function} */ (args.pop()); } else { validateStream = () => true; } for (let i = 0; i < args.length; i += 1) { const port = args[i]; /** @type Array<string> */ const portBrackets = []; let hasData = false; /** @type {HasValidationCallback} */ const validate = (ip) => { if (ip.type === 'openBracket') { portBrackets.push(ip.data); return false; } if (ip.type === 'data') { // Run the stream validation callback hasData = validateStream(ip, portBrackets); // Data IP on its own is a valid stream if (!portBrackets.length) { return hasData; } // Otherwise we need to check for complete stream return false; } if (ip.type === 'closeBracket') { portBrackets.pop(); if (portBrackets.length) { return false; } if (!hasData) { return false; } return true; } return false; }; if (!this.has(port, validate)) { return false; } } return true; } // ## Input processing // // Once preconditions have been met, the processing function can read from // the input buffers. Reading packets sets the component as "activated". // // Fetches IP object(s) for port(s) /** * @param {...GetArgument} params * @returns {void|IP|Array<IP|void>} */ get(...params) { this.activate(); let args = params; if (!args.length) { args = ['in']; } /** @type {Array<IP|void>} */ const res = []; for (let i = 0; i < args.length; i += 1) { const port = args[i]; let idx; let ip; let portname; if (Array.isArray(port)) { [portname, idx] = Array.from(port); if (!this.ports.ports[portname].isAddressable()) { throw new Error('Non-addressable ports, access must be with string portname'); } } else { portname = port; if (this.ports.ports[portname].isAddressable()) { throw new Error('For addressable ports, access must be with array [portname, idx]'); } } const name = /** @type {string} */ (portname); const idxName = /** @type {number} */ (idx); if (this.nodeInstance.isForwardingInport(name)) { ip = this.__getForForwarding(name, idxName); res.push(ip); } else { const portImpl = /** @type {import("./InPort").default} */ (this.ports.ports[name]); ip = portImpl.get(this.scope, idx); res.push(ip); } } if (args.length === 1) { return res[0]; } return res; } /** * @private * @param {string} port * @param {number} [idx] * @returns {IP|void} */ __getForForwarding(port, idx) { const prefix = []; let dataIp; // Read IPs until we hit data let ok = true; while (ok) { // Read next packet const portImpl = /** @type {import("./InPort").default} */ (this.ports.ports[port]); const ip = portImpl.get(this.scope, idx); // Stop at the end of the buffer if (!ip) { break; } if (ip.type === 'data') { // Hit the data IP, stop here dataIp = ip; ok = false; break; } // Keep track of bracket closings and openings before prefix.push(ip); } // Forwarding brackets that came before data packet need to manipulate context // and be added to result so they can be forwarded correctly to ports that // need them for (let i = 0; i < prefix.length; i += 1) { const ip = prefix[i]; if (ip.type === 'closeBracket') { // Bracket closings before data should remove bracket context if (!this.result.__bracketClosingBefore) { this.result.__bracketClosingBefore = []; } const context = this.nodeInstance.getBracketContext('in', port, this.scope, idx).pop(); context.closeIp = ip; this.result.__bracketClosingBefore.push(context); } else if (ip.type === 'openBracket') { // Bracket openings need to go to bracket context this.nodeInstance.getBracketContext('in', port, this.scope, idx).push({ ip, ports: [], source: port, }); } } // Add current bracket context to the result so that when we send // to ports we can also add the surrounding brackets if (!this.result.__bracketContext) { this.result.__bracketContext = {}; } this.result.__bracketContext[port] = this.nodeInstance.getBracketContext('in', port, this.scope, idx).slice(0); // Bracket closings that were in buffer after the data packet need to // be added to result for done() to read them from return dataIp; } // Fetches `data` property of IP object(s) for given port(s) /** * @param {...GetArgument} params * @returns {any|Array<any>} */ getData(...params) { let args = params; if (!args.length) { args = ['in']; } /** @type {Array<any>} */ const datas = []; args.forEach((port) => { let packet = /** @type {IP} */ (this.get(port)); if (packet == null) { // we add the null packet to the array so when getting // multiple ports, if one is null we still return it // so the indexes are correct. datas.push(packet); return; } while (packet.type !== 'data') { packet = /** @type {IP} */ (this.get(port)); if (!packet) { break; } } datas.push(packet.data); }); if (args.length === 1) { return datas.pop(); } return datas; } // Fetches a complete data stream from the buffer. /** * @param {...GetArgument} params * @returns {void|Array<IP>|Array<void|Array<IP>>} */ getStream(...params) { let args = params; if (!args.length) { args = ['in']; } /** @type {Array<Array<IP>|void>} */ const datas = []; for (let i = 0; i < args.length; i += 1) { const port = args[i]; const portBrackets = []; /** @type {Array<IP>} */ let portPackets = []; let hasData = false; let ip = /** @type {IP} */ (this.get(port)); if (!ip) { datas.push(undefined); } while (ip) { if (ip.type === 'openBracket') { if (!portBrackets.length) { // First openBracket in stream, drop previous portPackets = []; hasData = false; } portBrackets.push(ip.data); portPackets.push(ip); } if (ip.type === 'data') { portPackets.push(ip); hasData = true; // Unbracketed data packet is a valid stream if (!portBrackets.length) { break; } } if (ip.type === 'closeBracket') { portPackets.push(ip); portBrackets.pop(); if (hasData && !portBrackets.length) { // Last close bracket finishes stream if there was data inside break; } } ip = /** @type {IP} */ (this.get(port)); } datas.push(portPackets); } if (args.length === 1) { return datas[0]; } return datas; } } exports.default = ProcessInput;