Skip to content

Commit 1264873

Browse files
committed
Refactor core parser
1 parent 31757d0 commit 1264873

File tree

10 files changed

+96
-84
lines changed

10 files changed

+96
-84
lines changed

README.md

+7-6
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
[![License](https://img.shields.io/github/license/drsensor/scdlang.svg)](./LICENSE)
99
[![Chats](https://img.shields.io/badge/community-grey.svg?logo=matrix)](https://matrix.to/#/+statecharts:matrix.org)
1010

11-
> 🚧 Still **Work in Progress** 🏗️
11+
> 🚧 Slowly **Work in Progress** 🏗️
1212
1313
## About
1414
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
3535
- [ ] [WaveDrom](https://observablehq.com/@drom/wavedrom)
3636
- Compile into other formats (hopefully, no promise):
3737
- [ ] WebAssembly (using [parity-wasm](https://github.com/paritytech/parity-wasm))
38-
- Code generation 🤔
39-
- [ ] 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)
40-
- [ ] 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)
41-
- [ ] 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/) 💪
42-
- [ ] 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)
38+
- Code generation (all of them can be non-embedded and it will be the priority) 🤔
39+
- [ ] 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]
40+
- [ ] 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]
41+
- [ ] 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]
42+
- [ ] 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]
43+
- [ ] 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]
4344

4445
> For more info, see the changelog in the [release page][]
4546

packages/core/src/cache.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ use std::{collections::HashMap, sync::*};
88
// 🤔 or is there any better way?
99
// pub static mut TRANSITION: Option<HashMap<Transition, &str>> = None; // doesn't work!
1010
// type LazyMut<T> = Mutex<Option<T>>;
11-
static TRANSITION: Lazy<Mutex<MapTransition>> = Lazy::new(|| Mutex::new(HashMap::new()));
11+
static TRANSITION: Lazy<Mutex<TransitionMap>> = Lazy::new(|| Mutex::new(HashMap::new()));
1212
static WARNING: Lazy<RwLock<String>> = Lazy::new(|| RwLock::new(String::new()));
1313
/*reserved for another caches*/
1414

1515
/// Access cached transition safely
16-
pub fn transition<'a>() -> Result<MutexGuard<'a, MapTransition>, Error> {
16+
pub fn transition<'a>() -> Result<MutexGuard<'a, TransitionMap>, Error> {
1717
TRANSITION.lock().map_err(|_| Error::Deadlock)
1818
}
1919

@@ -55,7 +55,8 @@ impl Shrink {
5555
}
5656

5757
// TODO: 🤔 consider using this approach http://idubrov.name/rust/2018/06/01/tricking-the-hashmap.html
58-
pub(crate) type MapTransition = HashMap<CurrentState, HashMap<Trigger, NextState>>;
58+
pub(crate) type TransitionMap = HashMap<CurrentState, HashMap<Trigger, NextState>>;
59+
// pub(crate) type WarningSet = HashSet<CurrentState>;
5960
pub(crate) type CurrentState = String;
6061
pub(crate) type NextState = String;
6162
pub(crate) type Trigger = Option<String>;

packages/core/src/core/builder.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{cache, error::Error, external::Builder};
22
use pest_derive::Parser;
3-
use std::collections::HashMap;
3+
use std::collections::*;
44

55
#[derive(Debug, Parser, Default, Clone)] // 🤔 is it wise to derive from Copy&Clone ?
66
#[grammar = "grammar.pest"]
@@ -34,7 +34,6 @@ pub struct Scdlang<'g> {
3434

3535
pub(super) clear_cache: bool, //-|in case for program that need to disable…|
3636
pub semantic_error: bool, //-|…then enable semantic error at runtime|
37-
pub warnings: &'g [&'g str],
3837

3938
derive_config: Option<HashMap<&'static str, &'g str>>,
4039
}

packages/core/src/external.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,11 @@ pub trait Builder<'t> {
134134
/// Set the line_of_code offset of the error essages.
135135
fn with_err_line(&mut self, line: usize) -> &mut dyn Builder<'t>;
136136

137-
// Set custom config. Used on derived Parser.
137+
// WARNING: `Any` is not supported because trait object can't have generic methods
138+
139+
/// Set custom config. Used on derived Parser.
138140
fn set(&mut self, key: &'static str, value: &'t str) -> &mut dyn Builder<'t>;
139-
// Get custom config. Used on derived Parser.
141+
/// Get custom config. Used on derived Parser.
140142
fn get(&self, key: &'t str) -> Option<&'t str>;
141143
}
142144

packages/core/src/semantics/transition/analyze.rs

+3-68
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use super::helper::prelude::*;
1+
use super::helper::{prelude::*, transform_key::*};
22
use crate::{cache, semantics, utils::naming::sanitize, Error};
3-
use semantics::{analyze::*, Event, Kind, Transition};
3+
use semantics::{analyze::*, Kind, Transition};
44

55
impl SemanticCheck for Transition<'_> {
66
fn check_error(&self) -> Result<Option<String>, Error> {
@@ -52,63 +52,6 @@ impl SemanticCheck for Transition<'_> {
5252
}
5353
}
5454

55-
// WARNING: not performant because of using concatenated String as a key which cause filtering
56-
impl From<&Event<'_>> for String {
57-
fn from(event: &Event<'_>) -> Self {
58-
format!("{}?{}", event.name.unwrap_or(""), event.guard.unwrap_or(""))
59-
}
60-
}
61-
62-
impl<'i> EventKey<'i> for &'i Option<String> {}
63-
trait EventKey<'i>: Into<Option<&'i String>> {
64-
fn has_trigger(self) -> bool {
65-
self.into().filter(|e| is_empty(e.rsplit('?'))).is_some()
66-
}
67-
fn has_guard(self) -> bool {
68-
self.into().filter(|e| is_empty(e.split('?'))).is_some()
69-
}
70-
fn get_guard(self) -> Option<&'i str> {
71-
self.into().and_then(|e| none_empty(e.split('?')))
72-
}
73-
fn get_trigger(self) -> Option<&'i str> {
74-
self.into().and_then(|e| none_empty(e.rsplit('?')))
75-
}
76-
fn guards_with_same_trigger(self, trigger: Option<&'i str>) -> Option<&'i str> {
77-
self.into()
78-
.filter(|e| none_empty(e.rsplit('?')) == trigger)
79-
.and_then(|e| none_empty(e.split('?')))
80-
}
81-
fn triggers_with_same_guard(self, guard: Option<&'i str>) -> Option<&'i str> {
82-
self.into()
83-
.filter(|e| none_empty(e.split('?')) == guard)
84-
.and_then(|e| none_empty(e.rsplit('?')))
85-
}
86-
fn as_expression(self) -> String {
87-
self.into().map(String::as_str).as_expression()
88-
}
89-
}
90-
91-
impl<'o> Trigger<'o> for &'o Option<&'o str> {}
92-
trait Trigger<'o>: Into<Option<&'o &'o str>> {
93-
fn as_expression(self) -> String {
94-
self.into()
95-
.map(|s| {
96-
format!(
97-
" @ {trigger}{guard}",
98-
trigger = none_empty(s.rsplit('?')).unwrap_or_default(),
99-
guard = none_empty(s.split('?'))
100-
.filter(|_| s.contains('?'))
101-
.map(|g| format!("[{}]", g))
102-
.unwrap_or_default(),
103-
)
104-
})
105-
.unwrap_or_default()
106-
}
107-
fn as_key(self, guard: &str) -> Option<String> {
108-
Some(format!("{}?{}", self.into().unwrap_or(&""), guard))
109-
}
110-
}
111-
11255
impl<'t> SemanticAnalyze<'t> for Transition<'t> {
11356
fn analyze_error(&self, span: Span<'t>, options: &'t Scdlang) -> Result<(), Error> {
11457
let make_error = |message| options.err_from_span(span, message).into();
@@ -139,17 +82,9 @@ impl<'t> SemanticAnalyze<'t> for Transition<'t> {
13982
}
14083
}
14184

142-
fn is_empty<'a>(split: impl Iterator<Item = &'a str>) -> bool {
143-
none_empty(split).is_some()
144-
}
145-
146-
fn none_empty<'a>(split: impl Iterator<Item = &'a str>) -> Option<&'a str> {
147-
split.last().filter(|s| !s.is_empty())
148-
}
149-
15085
use std::collections::HashMap;
15186
type CacheMap = HashMap<Option<String>, String>;
152-
type CachedTransition<'state> = MutexGuard<'state, cache::MapTransition>;
87+
type CachedTransition<'state> = MutexGuard<'state, cache::TransitionMap>;
15388

15489
impl<'t> Transition<'t> {
15590
fn cache_current_state<'a>(&self, cache: &'t mut CachedTransition<'a>) -> &'t mut CacheMap {

packages/core/src/semantics/transition/iter.rs renamed to packages/core/src/semantics/transition/desugar.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//! Code for desugaring expression into multiple transition
2+
13
use crate::semantics;
24
use semantics::{Transition, TransitionType};
35
use std::iter::FromIterator;
@@ -8,7 +10,6 @@ impl<'i> IntoIterator for Transition<'i> {
810

911
fn into_iter(mut self) -> Self::IntoIter {
1012
TransitionIterator(match self.kind {
11-
/*FIXME: iterator for internal transition*/
1213
TransitionType::Normal | TransitionType::Internal => vec![self],
1314
TransitionType::Toggle => {
1415
self.kind = TransitionType::Normal;

packages/core/src/semantics/transition/helper.rs

+70
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,73 @@ pub(super) mod get {
8484
Event { name: event, guard }
8585
}
8686
}
87+
88+
/// analyze.rs helpers for transforming key for caches
89+
pub(super) mod transform_key {
90+
use crate::semantics::*;
91+
92+
// WARNING: not performant because of using concatenated String as a key which cause filtering
93+
impl From<&Event<'_>> for String {
94+
fn from(event: &Event<'_>) -> Self {
95+
format!("{}?{}", event.name.unwrap_or(""), event.guard.unwrap_or(""))
96+
}
97+
}
98+
99+
impl<'i> EventKey<'i> for &'i Option<String> {}
100+
pub trait EventKey<'i>: Into<Option<&'i String>> {
101+
fn has_trigger(self) -> bool {
102+
self.into().filter(|e| is_empty(e.rsplit('?'))).is_some()
103+
}
104+
fn has_guard(self) -> bool {
105+
self.into().filter(|e| is_empty(e.split('?'))).is_some()
106+
}
107+
fn get_guard(self) -> Option<&'i str> {
108+
self.into().and_then(|e| none_empty(e.split('?')))
109+
}
110+
fn get_trigger(self) -> Option<&'i str> {
111+
self.into().and_then(|e| none_empty(e.rsplit('?')))
112+
}
113+
fn guards_with_same_trigger(self, trigger: Option<&'i str>) -> Option<&'i str> {
114+
self.into()
115+
.filter(|e| none_empty(e.rsplit('?')) == trigger)
116+
.and_then(|e| none_empty(e.split('?')))
117+
}
118+
fn triggers_with_same_guard(self, guard: Option<&'i str>) -> Option<&'i str> {
119+
self.into()
120+
.filter(|e| none_empty(e.split('?')) == guard)
121+
.and_then(|e| none_empty(e.rsplit('?')))
122+
}
123+
fn as_expression(self) -> String {
124+
self.into().map(String::as_str).as_expression()
125+
}
126+
}
127+
128+
impl<'o> Trigger<'o> for &'o Option<&'o str> {}
129+
pub trait Trigger<'o>: Into<Option<&'o &'o str>> {
130+
fn as_expression(self) -> String {
131+
self.into()
132+
.map(|s| {
133+
format!(
134+
" @ {trigger}{guard}",
135+
trigger = none_empty(s.rsplit('?')).unwrap_or_default(),
136+
guard = none_empty(s.split('?'))
137+
.filter(|_| s.contains('?'))
138+
.map(|g| format!("[{}]", g))
139+
.unwrap_or_default(),
140+
)
141+
})
142+
.unwrap_or_default()
143+
}
144+
fn as_key(self, guard: &str) -> Option<String> {
145+
Some(format!("{}?{}", self.into().unwrap_or(&""), guard))
146+
}
147+
}
148+
149+
fn is_empty<'a>(split: impl Iterator<Item = &'a str>) -> bool {
150+
none_empty(split).is_some()
151+
}
152+
153+
fn none_empty<'a>(split: impl Iterator<Item = &'a str>) -> Option<&'a str> {
154+
split.last().filter(|s| !s.is_empty())
155+
}
156+
}

packages/core/src/semantics/transition/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
//! parse -> convert -> desugar -> analyze -> consume
2+
13
mod analyze;
24
mod convert;
5+
mod desugar;
36
mod helper;
4-
mod iter;
57

68
use crate::{
79
semantics::{analyze::*, Check, Expression, Found, Kind, Transition},

packages/transpiler/smcat/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ mod test {
327327
"from": "A",
328328
"to": "B",
329329
"color": "red",
330+
// FIXME: 👇 should be tested using regex
330331
"note": ["duplicate transient-transition: A -> B,C"]
331332
}]
332333
}),

packages/transpiler/xstate/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ scdlang = { path = "../../core", version = "0.2.1" }
1414
serde_json = "1"
1515
serde = { version = "1", features = ["derive"] }
1616
serde_with = { version = "1", features = ["json"] }
17-
voca_rs = "1"
17+
voca_rs = "1" # helper to convert Scdlang naming convention into DavidKPiano naming convention
1818

1919
[dev-dependencies]
2020
assert-json-diff = "1"

0 commit comments

Comments
 (0)