Skip to content

Instantly share code, notes, and snippets.

@CodeOtter
Last active August 8, 2022 16:25
Show Gist options
  • Save CodeOtter/ce718b80a24f9a2bfc3a4a43b0ad28d9 to your computer and use it in GitHub Desktop.
Save CodeOtter/ce718b80a24f9a2bfc3a4a43b0ad28d9 to your computer and use it in GitHub Desktop.
FlowNote

FlowNote

We're rapidly approaching a world where flow-based programming is taking the lead, but there isn't any useful notation to represent all of the complexities of flow management. Let's assume a basic flow that takes a click event, extracts the X and Y coordinates from it, and moves a player based on the extracted values.

+----------+     +-----------+               +------------+
| getClick +-----> extractXY +---------------> movePlayer |
+----------+     +-----------+   xyChannel   +------+-----+
                                                    |
                                                    |
                                                    |RangeError
                                                    |
                                                    |
                                         +----------v---------+
                                         | displayRangeError |
                                         +--------------------+

If this was a modern JavaScript function, it would look something like this:

const retry = require('async-retry')
const events = require('events').EventEmitter;
const emitter = new events.EventEmitter();

async main () {
  document.on('click', (e) => {
    emitter.emit('getClick', e)
    const { x, y } = await extractXY(await xyChannel(e))

    retry(async (bail, retried) => {
      if (retried === 3) {
        await logFailedClick()
        return bail()
      }

      try {
        const moveResults = await movePlayer(x, y)
        emitter.emit('movePlayer', moveResults)
      } catch (err) {
        if (err instanceof RangeError) {
          await displayRangeError(err)
          emitter.emit('displayRangeError', err)
        }
      }
    }, {
      retries: 3
    })
  })
}

main()

As you can tell, async/await control flow adds a level of complexity that isn't so intuitive to reason about. With FlowNote, we can generate this entire block of code with one line of code:

getClick -> extractXY* xyChannel{ retry: 3, onFail: logFailedClick }> movePlayer RangeError! displayRangeError

Grammar

The grammar.ohm file is below, you copy and paste it to https://ohmlang.github.io/editor/

Pending Features

  • Flow assignment and reuse.
  • Node/flow factories or jumping flow pathing.
  • Static flow analysis with path isolation.
  • Node and channel tagging for easier targeting.
  • Event dispatching based on node, flow assignment, and metanode completion.
  • Step-based state transformation and replayability
  • Flow reversal (TBD)
// Try this out at https://ohmlang.github.io/editor/
// Use these examples:
// mA -> meta(a,b,c)
// Z = mA -> mB
// mA -> mB#fixedB -> retryProcess RetryError! @fixedB
// getClick -> extractXY* xyChannel{ retry:3, onFail: bob }> movePlayer SyntaxError! displaySyntaxError
Empath {
Flow
= Path
| Assignment
| EmptyListOf<Nodes, Channel>
Assignment = var "=" Path
Path = NonemptyListOf<Nodes, Channel>
Nodes
= MetaNode
| Node
MetaNode = Node "(" ListOf<Node, ",">")"
Node
= JumpNode
| SilentNode
| IdentityNode
| StandardNode
JumpNode = "@" StandardNode
SilentNode = StandardNode "*"
IdentityNode = StandardNode "#" var
StandardNode = var Properties? ~"="
Channel
= ErrorChannel
| PlainChannel
| NamedChannel
ErrorChannel = var Properties? "!"
PlainChannel = "-" Properties? ">"
NamedChannel = var Properties? ">"
Properties = "{" ListOf<Property, ","> "}"
Property = var ":" var
var = (alnum|"_")+
string (a string literal) = "\"" (~"\"" ~"\n" any)* "\""
number (a number literal)
= digit* "." digit+ -- fract
| digit+ -- whole
space += comment
comment
= "/*" (~"*/" any)* "*/" -- multiLine
| "//" (~"\n" any)* -- singleLine
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment