IGraph and planning

IGraph and planning

The JSON form of a program, and how planGraph checks it once for both backends.

An IGraph is a program as data: a list of nodes and a list of connections. @hlang-org/ir defines its types and planGraph, and has no dependencies.

JavaScript
// The same program as data: an IGraph. A connection's Output is the upstream
// port and its Input is the downstream port.
export const graph = {
  nodeList: [
    { nodeId: 'a', code: 'greeting', name: 'Greeting', type: 'R' },
    { nodeId: 'b', code: 'uppercase', name: 'Uppercase', type: 'T' },
    { nodeId: 'c', code: 'print', name: 'Print', type: 'W' },
  ],
  connections: [
    { Output: { nodeId: 'a', portConf: { id: 'out' } }, Input: { nodeId: 'b', portConf: { id: 'in' } } },
    { Output: { nodeId: 'b', portConf: { id: 'out' } }, Input: { nodeId: 'c', portConf: { id: 'in' } } },
  ],
};

Each node has a nodeId (unique in the graph), a code (the node type a backend resolves), a name and a type: 'R', 'T' or 'W'. Parameters go in bizData.params. A connection's Output is the upstream port and its Input is the downstream port.

planGraph

planGraph(graph) checks the graph and returns an ExecutionPlan: its nodes, its connections in graph order, and its Readable roots. The interpreter and the compiler both start from this plan, so they apply the same rules.

JavaScript
import { planGraph, GraphError } from '@hlang-org/ir';
import { graph } from './lib/graph.mjs';

// planGraph checks a graph once and returns the plan both backends use.
const plan = planGraph(graph);
console.log('roots:', plan.roots.join(', '));
for (const { source, target } of plan.connections) {
  console.log(`${source.nodeId}.${source.portId} -> ${target.nodeId}.${target.portId}`);
}

// A Writable has no output port, so a connection from it is refused.
const reversed = {
  ...graph,
  connections: [{ Output: { nodeId: 'c', portConf: { id: 'in' } }, Input: { nodeId: 'a', portConf: { id: 'out' } } }],
};
try {
  planGraph(reversed);
} catch (error) {
  if (error instanceof GraphError) {
    console.log(`${error.code}: ${error.message}`);
  } else {
    throw error;
  }
}
Output
roots: a
a.out -> b.in
b.out -> c.in
invalid-connection: Writable node "c" has no output ports and cannot be a connection source.

Errors

A graph that breaks a rule throws a GraphError with one of these codes:

CodeMeaning
duplicate-node-idTwo nodes share a nodeId.
invalid-node-kindA node's type is not R, T or W.
unknown-nodeA connection names a node that is not in nodeList.
invalid-connectionA connection leaves a Writable node, enters a Readable node, or targets the @node/error port, which is an output.
no-readable-rootThe graph has no Readable node to run from.

The full API is in the ir reference.

The examples on this page run in CI. Edit this page on GitHub