diff --git a/README.md b/README.md index 5f14ed9f..1fc7b611 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![License](https://img.shields.io/github/license/drsensor/scdlang.svg)](./LICENSE) [![Chats](https://img.shields.io/badge/community-grey.svg?logo=matrix)](https://matrix.to/#/+statecharts:matrix.org) -> 🚧 Still **Work in Progress** 🏗️ +> 🚧 Slowly **Work in Progress** 🏗️ ## About Scdlang (pronounced `/ˈesˌsi:ˈdi:ˈlæŋ/`) is a description language for describing Statecharts that later can be used to generate code or just transpile it into another format. This project is more focus on how to describe Statecharts universally that can be used in another language/platform rather than drawing a Statecharts diagram. For drawing, see [State Machine Cat][]. @@ -35,11 +35,12 @@ Scdlang (pronounced `/ˈesˌsi:ˈdi:ˈlæŋ/`) is a description language for des - [ ] [WaveDrom](https://observablehq.com/@drom/wavedrom) - Compile into other formats (hopefully, no promise): - [ ] WebAssembly (using [parity-wasm](https://github.com/paritytech/parity-wasm)) -- Code generation 🤔 - - [ ] Julia via [`@generated`](https://docs.julialang.org/en/v1/manual/metaprogramming/#Generated-functions-1) implemented as [parametric](https://docs.julialang.org/en/v1/manual/methods/#Parametric-Methods-1) [multiple-dispatch](https://en.wikipedia.org/wiki/Multiple_dispatch#Julia) [functors](https://docs.julialang.org/en/v1/manual/methods/#Function-like-objects-1) - - [ ] Rust via [`#[proc_macro_attribute]`](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) implemented as [typestate programming](https://rust-embedded.github.io/book/static-guarantees/typestate-programming.html)? (I'm still afraid if it will conflict with another crates) - - [ ] Elixir via [`use`](https://elixir-lang.org/getting-started/alias-require-and-import.html#use) macro which desugar into [gen_statem](https://andrealeopardi.com/posts/connection-managers-with-gen_statem/) 💪 - - [ ] Flutter via [`builder_factories`](https://github.com/flutter/flutter/wiki/Code-generation-in-Flutter) (waiting for the [FFI](https://github.com/dart-lang/sdk/issues/34452) to be stable) +- Code generation (all of them can be non-embedded and it will be the priority) 🤔 + - [ ] Julia via [`@generated`](https://docs.julialang.org/en/v1/manual/metaprogramming/#Generated-functions-1) implemented as [parametric](https://docs.julialang.org/en/v1/manual/methods/#Parametric-Methods-1) [multiple-dispatch](https://en.wikipedia.org/wiki/Multiple_dispatch#Julia) [functors](https://docs.julialang.org/en/v1/manual/methods/#Function-like-objects-1) [non-embedded until there is a project like PyO3 but for Julia] + - [ ] Rust via [`#[proc_macro_attribute]`](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) implemented as [typestate programming](https://rust-embedded.github.io/book/static-guarantees/typestate-programming.html)? (Need to figure out how to support async, non-async, and no-std in single abstraction) [embedded] + - [ ] Elixir via [`use`](https://elixir-lang.org/getting-started/alias-require-and-import.html#use) macro and [rustler](https://github.com/rusterlium/rustler) which desugar into [gen_statem](https://andrealeopardi.com/posts/connection-managers-with-gen_statem/) [embedded] + - [ ] Flutter via [`builder_factories`](https://github.com/flutter/flutter/wiki/Code-generation-in-Flutter) (waiting for the [FFI](https://github.com/dart-lang/sdk/issues/34452) to be stable) [embedded] + - [ ] Typescript or **AssemblyScript** implemented as [typestate interface or abstract-class](https://spectrum.chat/statecharts/general/typestate-guard~d1ec4eb1-6db7-45bb-8b79-836c9a22cd5d) (usefull for building smart contract) [non-embedded] > For more info, see the changelog in the [release page][] diff --git a/packages/core/src/cache.rs b/packages/core/src/cache.rs index e83cd9f5..2f3de240 100644 --- a/packages/core/src/cache.rs +++ b/packages/core/src/cache.rs @@ -8,12 +8,12 @@ use std::{collections::HashMap, sync::*}; // 🤔 or is there any better way? // pub static mut TRANSITION: Option> = None; // doesn't work! // type LazyMut = Mutex>; -static TRANSITION: Lazy> = Lazy::new(|| Mutex::new(HashMap::new())); +static TRANSITION: Lazy> = Lazy::new(|| Mutex::new(HashMap::new())); static WARNING: Lazy> = Lazy::new(|| RwLock::new(String::new())); /*reserved for another caches*/ /// Access cached transition safely -pub fn transition<'a>() -> Result, Error> { +pub fn transition<'a>() -> Result, Error> { TRANSITION.lock().map_err(|_| Error::Deadlock) } @@ -55,7 +55,8 @@ impl Shrink { } // TODO: 🤔 consider using this approach http://idubrov.name/rust/2018/06/01/tricking-the-hashmap.html -pub(crate) type MapTransition = HashMap>; +pub(crate) type TransitionMap = HashMap>; +// pub(crate) type WarningSet = HashSet; pub(crate) type CurrentState = String; pub(crate) type NextState = String; pub(crate) type Trigger = Option; diff --git a/packages/core/src/core/builder.rs b/packages/core/src/core/builder.rs index 94326d83..2e5d8925 100644 --- a/packages/core/src/core/builder.rs +++ b/packages/core/src/core/builder.rs @@ -1,6 +1,6 @@ use crate::{cache, error::Error, external::Builder}; use pest_derive::Parser; -use std::collections::HashMap; +use std::collections::*; #[derive(Debug, Parser, Default, Clone)] // 🤔 is it wise to derive from Copy&Clone ? #[grammar = "grammar.pest"] @@ -34,7 +34,6 @@ pub struct Scdlang<'g> { pub(super) clear_cache: bool, //-|in case for program that need to disable…| pub semantic_error: bool, //-|…then enable semantic error at runtime| - pub warnings: &'g [&'g str], derive_config: Option>, } diff --git a/packages/core/src/external.rs b/packages/core/src/external.rs index 64535dc7..5a252e9d 100644 --- a/packages/core/src/external.rs +++ b/packages/core/src/external.rs @@ -134,9 +134,11 @@ pub trait Builder<'t> { /// Set the line_of_code offset of the error essages. fn with_err_line(&mut self, line: usize) -> &mut dyn Builder<'t>; - // Set custom config. Used on derived Parser. + // WARNING: `Any` is not supported because trait object can't have generic methods + + /// Set custom config. Used on derived Parser. fn set(&mut self, key: &'static str, value: &'t str) -> &mut dyn Builder<'t>; - // Get custom config. Used on derived Parser. + /// Get custom config. Used on derived Parser. fn get(&self, key: &'t str) -> Option<&'t str>; } diff --git a/packages/core/src/semantics/transition/analyze.rs b/packages/core/src/semantics/transition/analyze.rs index b5822396..73777ad4 100644 --- a/packages/core/src/semantics/transition/analyze.rs +++ b/packages/core/src/semantics/transition/analyze.rs @@ -1,6 +1,6 @@ -use super::helper::prelude::*; +use super::helper::{prelude::*, transform_key::*}; use crate::{cache, semantics, utils::naming::sanitize, Error}; -use semantics::{analyze::*, Event, Kind, Transition}; +use semantics::{analyze::*, Kind, Transition}; impl SemanticCheck for Transition<'_> { fn check_error(&self) -> Result, Error> { @@ -52,63 +52,6 @@ impl SemanticCheck for Transition<'_> { } } -// WARNING: not performant because of using concatenated String as a key which cause filtering -impl From<&Event<'_>> for String { - fn from(event: &Event<'_>) -> Self { - format!("{}?{}", event.name.unwrap_or(""), event.guard.unwrap_or("")) - } -} - -impl<'i> EventKey<'i> for &'i Option {} -trait EventKey<'i>: Into> { - fn has_trigger(self) -> bool { - self.into().filter(|e| is_empty(e.rsplit('?'))).is_some() - } - fn has_guard(self) -> bool { - self.into().filter(|e| is_empty(e.split('?'))).is_some() - } - fn get_guard(self) -> Option<&'i str> { - self.into().and_then(|e| none_empty(e.split('?'))) - } - fn get_trigger(self) -> Option<&'i str> { - self.into().and_then(|e| none_empty(e.rsplit('?'))) - } - fn guards_with_same_trigger(self, trigger: Option<&'i str>) -> Option<&'i str> { - self.into() - .filter(|e| none_empty(e.rsplit('?')) == trigger) - .and_then(|e| none_empty(e.split('?'))) - } - fn triggers_with_same_guard(self, guard: Option<&'i str>) -> Option<&'i str> { - self.into() - .filter(|e| none_empty(e.split('?')) == guard) - .and_then(|e| none_empty(e.rsplit('?'))) - } - fn as_expression(self) -> String { - self.into().map(String::as_str).as_expression() - } -} - -impl<'o> Trigger<'o> for &'o Option<&'o str> {} -trait Trigger<'o>: Into> { - fn as_expression(self) -> String { - self.into() - .map(|s| { - format!( - " @ {trigger}{guard}", - trigger = none_empty(s.rsplit('?')).unwrap_or_default(), - guard = none_empty(s.split('?')) - .filter(|_| s.contains('?')) - .map(|g| format!("[{}]", g)) - .unwrap_or_default(), - ) - }) - .unwrap_or_default() - } - fn as_key(self, guard: &str) -> Option { - Some(format!("{}?{}", self.into().unwrap_or(&""), guard)) - } -} - impl<'t> SemanticAnalyze<'t> for Transition<'t> { fn analyze_error(&self, span: Span<'t>, options: &'t Scdlang) -> Result<(), Error> { let make_error = |message| options.err_from_span(span, message).into(); @@ -139,17 +82,9 @@ impl<'t> SemanticAnalyze<'t> for Transition<'t> { } } -fn is_empty<'a>(split: impl Iterator) -> bool { - none_empty(split).is_some() -} - -fn none_empty<'a>(split: impl Iterator) -> Option<&'a str> { - split.last().filter(|s| !s.is_empty()) -} - use std::collections::HashMap; type CacheMap = HashMap, String>; -type CachedTransition<'state> = MutexGuard<'state, cache::MapTransition>; +type CachedTransition<'state> = MutexGuard<'state, cache::TransitionMap>; impl<'t> Transition<'t> { fn cache_current_state<'a>(&self, cache: &'t mut CachedTransition<'a>) -> &'t mut CacheMap { diff --git a/packages/core/src/semantics/transition/iter.rs b/packages/core/src/semantics/transition/desugar.rs similarity index 96% rename from packages/core/src/semantics/transition/iter.rs rename to packages/core/src/semantics/transition/desugar.rs index 44569e13..4a042aaa 100644 --- a/packages/core/src/semantics/transition/iter.rs +++ b/packages/core/src/semantics/transition/desugar.rs @@ -1,3 +1,5 @@ +//! Code for desugaring expression into multiple transition + use crate::semantics; use semantics::{Transition, TransitionType}; use std::iter::FromIterator; @@ -8,7 +10,6 @@ impl<'i> IntoIterator for Transition<'i> { fn into_iter(mut self) -> Self::IntoIter { TransitionIterator(match self.kind { - /*FIXME: iterator for internal transition*/ TransitionType::Normal | TransitionType::Internal => vec![self], TransitionType::Toggle => { self.kind = TransitionType::Normal; diff --git a/packages/core/src/semantics/transition/helper.rs b/packages/core/src/semantics/transition/helper.rs index ba133391..b31c0340 100644 --- a/packages/core/src/semantics/transition/helper.rs +++ b/packages/core/src/semantics/transition/helper.rs @@ -84,3 +84,73 @@ pub(super) mod get { Event { name: event, guard } } } + +/// analyze.rs helpers for transforming key for caches +pub(super) mod transform_key { + use crate::semantics::*; + + // WARNING: not performant because of using concatenated String as a key which cause filtering + impl From<&Event<'_>> for String { + fn from(event: &Event<'_>) -> Self { + format!("{}?{}", event.name.unwrap_or(""), event.guard.unwrap_or("")) + } + } + + impl<'i> EventKey<'i> for &'i Option {} + pub trait EventKey<'i>: Into> { + fn has_trigger(self) -> bool { + self.into().filter(|e| is_empty(e.rsplit('?'))).is_some() + } + fn has_guard(self) -> bool { + self.into().filter(|e| is_empty(e.split('?'))).is_some() + } + fn get_guard(self) -> Option<&'i str> { + self.into().and_then(|e| none_empty(e.split('?'))) + } + fn get_trigger(self) -> Option<&'i str> { + self.into().and_then(|e| none_empty(e.rsplit('?'))) + } + fn guards_with_same_trigger(self, trigger: Option<&'i str>) -> Option<&'i str> { + self.into() + .filter(|e| none_empty(e.rsplit('?')) == trigger) + .and_then(|e| none_empty(e.split('?'))) + } + fn triggers_with_same_guard(self, guard: Option<&'i str>) -> Option<&'i str> { + self.into() + .filter(|e| none_empty(e.split('?')) == guard) + .and_then(|e| none_empty(e.rsplit('?'))) + } + fn as_expression(self) -> String { + self.into().map(String::as_str).as_expression() + } + } + + impl<'o> Trigger<'o> for &'o Option<&'o str> {} + pub trait Trigger<'o>: Into> { + fn as_expression(self) -> String { + self.into() + .map(|s| { + format!( + " @ {trigger}{guard}", + trigger = none_empty(s.rsplit('?')).unwrap_or_default(), + guard = none_empty(s.split('?')) + .filter(|_| s.contains('?')) + .map(|g| format!("[{}]", g)) + .unwrap_or_default(), + ) + }) + .unwrap_or_default() + } + fn as_key(self, guard: &str) -> Option { + Some(format!("{}?{}", self.into().unwrap_or(&""), guard)) + } + } + + fn is_empty<'a>(split: impl Iterator) -> bool { + none_empty(split).is_some() + } + + fn none_empty<'a>(split: impl Iterator) -> Option<&'a str> { + split.last().filter(|s| !s.is_empty()) + } +} diff --git a/packages/core/src/semantics/transition/mod.rs b/packages/core/src/semantics/transition/mod.rs index 524c5131..b68842b3 100644 --- a/packages/core/src/semantics/transition/mod.rs +++ b/packages/core/src/semantics/transition/mod.rs @@ -1,7 +1,9 @@ +//! parse -> convert -> desugar -> analyze -> consume + mod analyze; mod convert; +mod desugar; mod helper; -mod iter; use crate::{ semantics::{analyze::*, Check, Expression, Found, Kind, Transition}, diff --git a/packages/transpiler/smcat/src/lib.rs b/packages/transpiler/smcat/src/lib.rs index 89cb1098..4f507a4a 100644 --- a/packages/transpiler/smcat/src/lib.rs +++ b/packages/transpiler/smcat/src/lib.rs @@ -327,6 +327,7 @@ mod test { "from": "A", "to": "B", "color": "red", + // FIXME: 👇 should be tested using regex "note": ["duplicate transient-transition: A -> B,C"] }] }), diff --git a/packages/transpiler/xstate/Cargo.toml b/packages/transpiler/xstate/Cargo.toml index e4b4981f..90df6626 100644 --- a/packages/transpiler/xstate/Cargo.toml +++ b/packages/transpiler/xstate/Cargo.toml @@ -14,7 +14,7 @@ scdlang = { path = "../../core", version = "0.2.1" } serde_json = "1" serde = { version = "1", features = ["derive"] } serde_with = { version = "1", features = ["json"] } -voca_rs = "1" +voca_rs = "1" # helper to convert Scdlang naming convention into DavidKPiano naming convention [dev-dependencies] assert-json-diff = "1" \ No newline at end of file