Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MermaidJS to Superstate #1

Open
tomByrer opened this issue Apr 8, 2024 · 6 comments
Open

MermaidJS to Superstate #1

tomByrer opened this issue Apr 8, 2024 · 6 comments

Comments

@tomByrer
Copy link

tomByrer commented Apr 8, 2024

FYI I might make another target to translate Mermaid JS to Superstate.

I'm already 1/2 way there; since I knew there would be multiple types of sources & targets for statecharts, I made an intervening output (sort of simple AST), so the only step is to go from that Object notation to Superstate.
EG:
source:

stateDiagram-v2
id mermaidJS state diagram
%% inspiration: https://mermaid.js.org/syntax/stateDiagram.html
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still : STOP %% named transition / event
Moving --> Crash
Crash --> [*]
Loading

to OSM:

{
  "final": [
    "Still",
    "Crash",
  ],
  "id": "mermaidJS state diagram",
  "initial": "Still",
  "isConcurrent": false,
  "states": {
    "Crash": {
      "Crash--FINIS": "FINIS",
    },
    "FINIS": "final",
    "Moving": {
      "Moving--Crash": "Crash",
      "STOP": "Still",
    },
    "Still": {
      "Still--FINIS": "FINIS",
      "Still--Moving": "Moving",
    },
  },
}

Which in tern is used to make a XState machine

import { createMachine } from "xstate";

export const machinemermaidJSstatediagram = createMachine({
	id: "mermaidJS state diagram",
	initial: "Still",
	states: {
		Still: {
			on: {
				"Still--Moving": {
					target: "Moving"
				},
				"Still--FINIS": {
					target: "FINIS"
				}
			}
		},
		Moving: {
			on: {
				STOP: {
					target: "Still"
				},
				"Moving--Crash": {
					target: "Crash"
				}
			}
		},
		Crash: {
			on: {
				"Crash--FINIS": {
					target: "FINIS"
				}
			}
		},
		FINIS: {
			type: "final"
		}
	}
},);
@kossnocorp
Copy link
Owner

Hey! This is fascinating! Can you tell me more about how this can be applied? I imagine this can be used to initialize a state chart, but then Superstate will lose all the type safety.

If it's to generate code, I'm curious if there's a way to synchronize and define substrates separately from the root statechart?

@kossnocorp
Copy link
Owner

By the way, I plan to do the vice-versa translation out of the box, so you can get Mermaid code from any statechart, which is admittedly much easier.

@tomByrer
Copy link
Author

tomByrer commented Apr 8, 2024

type safety

Yea, it was weird enough to add branching & "actions" to MermaidJS, & I still don't have context. + I'm quoting "actions" because there are no functions in MermaidJS, so I only allow to tag where the actions & filters get called. I expect folks have to hand-code actual code after conversion.

vice-versa translation

Well, I didn't think of OSM to Mermaid transcoding, but here is other example of the intervening pseudo-AST:
https://github.com/tomByrer/markdown2statemachine/blob/master/test/__snapshots__/snap.test.js.snap#L908C1-L944C5
Might be a bit verbose since I tend to keep in many empty values, & order is reversed. I also keep in the weird markup I invented for actions & guards in the OSM. But otherwise readable if you're familiar with XState.

Maybe there is a better way to deal with actions & guards better in the OSM?

If there was a common AST-ish for all things StateMachine, then not only documentation would be easier for all StateMachines (eg SuperState -> Mermaid), but different SM implementations can be used in the same workflow.

(Prior art for front-end development: Mitosis)

@davidkpiano
Copy link

If there was a common AST-ish for all things StateMachine, then not only documentation would be easier for all StateMachines (eg SuperState -> Mermaid), but different SM implementations can be used in the same workflow.

I'd be happy working towards this goal. I have started a statechart schema based on XState here: https://github.com/statelyai/specification/blob/main/schemas/statechart.json

@kossnocorp
Copy link
Owner

@tomByrer I just shipped superstate@1.0.0-beta.2 with the toMermaid function, which I think might be helpful. I used the XState approach to formatting actions, events, and guards.

I don't think I have access to the repo (https://github.com/tomByrer/markdown2statemachine), so I can't check how close it is to what you got right now.

Even though it could be beneficial for Superstate to render events as eventName() and actions as actionName! (as that's the convention in the code), having compatibility with XState, at least behind an option, will be the best bet.

@davidkpiano I'm not sure if you plan to change it or expand it, but I would appreciate your input.

I'm talking about those (plus guarded events if [long]):

image

If there was a common AST-ish for all things StateMachine, then not only documentation would be easier for all StateMachines (eg SuperState -> Mermaid), but different SM implementations can be used in the same workflow.

That's a fascinating idea. I'm unsure if @davidkpiano designed the spec to be universal, but we can at least use a subset of it.


Here's how the Mermaid rendering works:

import { superstate } from "superstate";
import { toMermaid } from "superstate/mermaid";

type VolumeState = "low" | "medium" | "high";

const volumeState = superstate<VolumeState>("volume")
  .state("low", "up() -> medium")
  .state("medium", ["up() -> high", "down() -> low"])
  .state("high", "down() -> medium");

type PlayerState = "stopped" | "playing" | "paused";

const playerState = superstate<PlayerState>("player")
  .state("stopped", "play() -> playing")
  .state("playing", ["pause() -> paused", "stop() -> stopped"], ($) =>
    $.sub("volume", volumeState)
  )
  .state("paused", ["play() -> playing", "stop() -> stopped"]);

const mermaid = toMermaid(playerState);

And produces:

%% Generated with Superstate
stateDiagram-v2
	state "player" as player {
		[*] --> player.stopped
		player.stopped --> player.playing : play
		player.playing --> player.paused : pause
		player.playing --> player.stopped : stop
		player.paused --> player.playing : play
		player.paused --> player.stopped : stop
		state "stopped" as player.stopped
		state "playing" as player.playing {
			[*] --> player.playing.low
			player.playing.low --> player.playing.medium : up
			player.playing.medium --> player.playing.high : up
			player.playing.medium --> player.playing.low : down
			player.playing.high --> player.playing.medium : down
			state "low" as player.playing.low
			state "medium" as player.playing.medium
			state "high" as player.playing.high
		}
		state "paused" as player.paused
	}
Loading

Mermaid code:

%% Generated with Superstate
stateDiagram-v2
	state "player" as player {
		[*] --> player.stopped
		player.stopped --> player.playing : play
		player.playing --> player.paused : pause
		player.playing --> player.stopped : stop
		player.paused --> player.playing : play
		player.paused --> player.stopped : stop
		state "stopped" as player.stopped
		state "playing" as player.playing {
			[*] --> player.playing.low
			player.playing.low --> player.playing.medium : up
			player.playing.medium --> player.playing.high : up
			player.playing.medium --> player.playing.low : down
			player.playing.high --> player.playing.medium : down
			state "low" as player.playing.low
			state "medium" as player.playing.medium
			state "high" as player.playing.high
		}
		state "paused" as player.paused
	}

@davidkpiano
Copy link

That's a fascinating idea. I'm unsure if @davidkpiano designed the spec to be universal, but we can at least use a subset of it.

Yes, I intend for the specification to be universal 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants