Share your Programming Language Jam submissions here!

← Back to all posts
Logic State - A State Machine Language
h
drsensor (16)

You need a diagram to picture the soundness of your system

A picture is worth a thousand words while a symbol is worth a hundred words

Let me start with meme-like

👇 Goal 👇

conversation between me and @brucou

The plan for this jam is to make the scope as minimal as possible and provide a real usage demo.
However...

👇 Reality 👇

This is what we get 😂 (sorry @brucou)

History

This language is a rewrite (and also redesign) of my previous prototype called Scdlang. It starts from my curiosity on how to define state machine in Rust which I'm new to that language back then (more info). As I experiment, the design of the language grow and I found a lot of interesting tools for the state machine. However, I can't find a language that similar to drawing-language like Graphviz or Mermaid but has semantics analysis and can generate code or be embedded in another programming language.

I've been discussing the design in the past with @brucou (see issue #29) but then I just realize that the design of this language become too wild and Rust macro is too slow. So I took this chance to rewrite it from scratch with a different approach.

Ah yes, he is the author of Kingly which is another state-machine Javascript library that has nice integration with several visualization tools. I highly recommend checking the docs.

Design Philosophy

Logic State share the same philosophy with Scdlang, which is:

  • Readable just like you read then visualize a state diagram
  • Writeable just like you write code which is concise and can be refactored
  • Transferable to any implementation (e.g platform, programming language, runtime, etc)

Readable

This can be achieved by utilizing symbols (and ligature-symbol) to mimic the diagram. However, the symbol should be meaningful. Let me take an example from smcat language:


instead of

"on backlog" => doing : working on it;

just write it as

on_backlog -> doing @ working_on_it

which can be read as on backlog into doing at working on it


The thing that needs to be considered is not having too many symbols in one-line.

My rule thumbs for this are:

  • the ratio between words and symbols/ligature-symbols is >5:<5 - Mean that the word count of one file/line must be same or less than the symbol or ligature-symbol count
  • avoid repetitive symbol - For example, there no things like double bracket (e.g [[, ))), etc) like in lisp. However, ligature-symbol like multiple dashes (-----) or such are allowed.

:: there are 2 rules cause I only have 2 thumbs 🥁 (pun intended)

Writeable

This part can be divided into 2:

  • conciseness
  • refactorability

Conciseness can be achieved by having syntactic sugar. For example:


when <-> where @ ask

will desugar into

when -> where @ ask
when <- where @ ask

also (work in progress)

when,what,why -----> who @ conclusion

desugar into

who <- when @ conclusion
who <- what @ conclusion
who <- why @ conclusion

As for refactorability, the language might have a module/import system (not decided) and hierarchical states (#29) in the future. However, this will not happen in a short time to keep the scope of this project as small as possible so it can be embedded in another programming language. Maybe there are better alternatives 🤔

Transferable

this part has relations on why full rewrite to Nim is necessary

This is the trickiest part. Let's try to relate state-machine between its use cases and the programming languages it commonly used:

  • Game - C#, Lua, ... , and Webassembly
  • Embedded System - C/C++/Rust for microcontroller; many HDL variants for ASIC design;
  • Apps - Java/Typescript, Kotlin, Dart, ... , and Webassembly
  • Server/Services - Erlang, Elixir, Akka, ...

Hmm, but that doesn't answer anything 🤔

Now relate them how state machine are implemented (mostly):

That's much better 😲

However, Logic State is a domain-specific language and there are several ways how DSLs are used:

  • embedded into programming language (e.g GraphpQL, lit-html, LINQ, regex)
  • single source of truth (e.g GraphQL, protobuf, EBNF)
  • sent over a wire (e.g GraphQL)

To make state machine language that can last in the long term, we need to support all those scenarios. That sound impossible but we can chip it down gradually as long as we use the right approach 😉

There is 2 aspect of how we can design this language:

  • State Machine as a runtime - this is a case when you need a system with dynamic behavior
  • State Machine as a generated code - this is a case when you need a system with unchanged/static behavior

And due to that complexity, we need to design Logic State as embeddable DSL first. Compiler for generating code can be implemented in any language whatever it uses VM or not. However, to make it compile at runtime is a different matter. We need to approach it on how PCRE (regex) or Rosie does. In short, we also need to package it as a static library, dynamic (shared) library, and webassembly module. This means the most suitable language for writing the compiler is: C/C++, Rust, Nim, or anything that can do manual memory management or have a very slim garbage collector.

Making 2 separate compilers is also an option. Where one is for generating code and the other is to be used at runtime. However, maintaining 2 implementations without a single source of truth can be quite difficult. And so there is 3 way we can approach this:

  • create an AST,
  • use parser combinator, or
  • just make composable grammar.

We use the later. Composable grammar is much easier to work with but there are very limited libraries or languages that support this. Raku is the best language for writing composable grammar but it needs MoarVM. Luckily Nim has a library called NPeg to write composable grammar.

In conclusion, Nim should be used to write the compiler because it brings these benefits:

[side-note] Nim macros are faster than Rust macros.

Roadmap

Short term

  • embedded into Javascript via tagged templates or babel macro. This can serve as a reference on how to embed it in other languages.
  • rich toolings
    • formatter
    • language server
    • syntax highlighting
    • repl terminal (already implemented but not yet polished)
    • analyzer for various usages. One of them is to get information how many times a state is visited and events that need to be triggered when traveling into a specific state.
    • interactive visualization
  • real usage demo

Long Term

  • expand the compiler to support 3 types of DSL:
    • embedded DSL which means it can be embedded into other languages or platforms. The DSL should have the most minimal subset syntax (e.g NO: import system, hierarchial state, etc) to keep it performant.
    • external DSL which means it can be used as a single source of truth. However, it needs a module or import system to make it composable.
    • extendable DSL, similiar to markdown maskfile or kakoune config which use special syntax to integrate with another languages.
  • integrate into various programming languages, including actor-model languages like Erlang/Elixir
  • better packaging or have plugin systems. Anything that can make non-core contribution and integration easier.

Conclusion

A bit out of topic but I've been participating in various game jams this past month here and learn a lot from another. So I've been thinking that I need an outlet to experiment and grow this language in a fun way. The game that I make mostly is vehicular combat (though I prefer to call it car battle) and I'm thinking to integrate this language where the player must constantly write a state machine to control the vehicle and defeat the enemy. So that even if I can't develop this language in fulltime, I can still develop it for my entertainment since using it in production might be too risky and can put people off.

Currently, this language supports generating code for 3 programming languages:

LanguageStatemachine Implementation
Javascript/TypescriptType State, State Pattern, Record/Collection
Typescript InterfaceType State, Record/Collection
Rust TraitType State
RustType State, Conditional Statement
GDScriptState Pattern

Team


P.S: it takes some time to Run ▶ the demo (examples/detective.logic) because it also needs to compile the source first (approx 5-15 seconds)

Please let me know if anyone figures out how to display the diagram side-by-side in the demo 🥺

Commentshotnewtop
drsensor (16)

@Vandesm14 thank you. This is the first time I use Nim in a project 🤠