Skip to content

Instantly share code, notes, and snippets.

@pekeler
Created October 12, 2025 15:58
Show Gist options
  • Select an option

  • Save pekeler/e600792ea39cf03d1a0e9d073d4ceb60 to your computer and use it in GitHub Desktop.

Select an option

Save pekeler/e600792ea39cf03d1a0e9d073d4ceb60 to your computer and use it in GitHub Desktop.
Converts a Robot state machine to D2 diagram syntax
/**
* Converts a Robot state machine to D2 diagram syntax
*
* Generates a statechart-style visualization with:
* - States, transitions (with event labels), and immediate transitions
* - Guard conditions indicated with [...] notation (e.g., '[...] event')
* - Current state highlighting, final state indicators, invoke state coloring
* - Support for nested machines with recursive rendering
*
* @param {object} machine - A Robot state machine created with createMachine()
* @returns {string} D2 diagram syntax ready to render
*
* @see https://d2lang.com - D2 diagramming language
* @see https://regularhuman.dev - Robot state machine library
* @see https://statecharts.dev - Statechart concepts
*/
export const r2d2 = (machine) => {
const machineLines = (machine, level = 0) => {
const states = Object.entries(machine.states).map(([stateName, state]) => {
const isCurrent = stateName === machine.current;
const lines = [
...(isCurrent
? state.fn || state.machine
? [`${stateName}.style.stroke: blue`, `${stateName}.style.stroke-width: 3`]
: [`${stateName}.style.fill: lightblue`]
: []),
...(state.final ? [`${stateName}.style.double-border: true`] : []),
...(state.fn ? [`${stateName}.style.fill: orange`] : []),
...(state.machine ? [`${stateName}.style.fill: ${level % 2 ? 'yellow' : 'lightyellow'}`] : []),
...Array.from(state.transitions ?? []).flatMap(([event, transList]) =>
transList.map((transition) => {
const guardPrefix = transition.guards.name !== 'truthy' ? '[...] ' : '';
return `${stateName} -> ${transition.to}: '${guardPrefix}${event}'`;
})
),
...(state.immediates?.map((immediate) => {
const guardPrefix = immediate.guards.name !== 'truthy' ? '[...] ' : '';
const label = guardPrefix ? `: '${guardPrefix.trim()}'` : '';
return `${stateName} -> ${immediate.to}${label}`;
}) ?? [])
];
if (state.machine) {
const nested = machineLines(state.machine, level + 1);
return {
lines: [...lines, `${stateName}: {`, ...nested.lines.map((line) => ` ${line}`), `}`],
maxLength: Math.max(stateName.length, nested.maxLength)
};
}
return { lines, maxLength: stateName.length };
});
return {
lines: states.flatMap((state) => state.lines),
maxLength: Math.max(...states.map((state) => state.maxLength))
};
};
const { lines, maxLength } = machineLines(machine, 1);
return [...lines, `*.style.border-radius: 25`, `*.width: ${maxLength * 13}`].join('\n');
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment