├── doc ├── .gitignore ├── demo.apng ├── book.toml └── src │ ├── examples.md │ ├── using-prompt.md │ ├── SUMMARY.md │ ├── using-derive.md │ ├── calling-methods.md │ ├── registering-data.md │ ├── attributes.md │ ├── modify-data.md │ ├── access-data.md │ ├── README.md │ └── examples │ ├── alacritty.md │ └── actix.md ├── interact ├── .gitignore ├── README.md ├── LICENSE-MIT ├── crates-io.md ├── LICENSE-APACHE ├── src │ ├── util │ │ ├── mod.rs │ │ ├── assist.rs │ │ ├── expect.rs │ │ └── node_tree.rs │ ├── access │ │ ├── iter.rs │ │ ├── explicit.rs │ │ ├── instant.rs │ │ ├── derive.rs │ │ ├── mutex.rs │ │ ├── refcell.rs │ │ ├── hashset.rs │ │ ├── basic.rs │ │ ├── derefs.rs │ │ ├── btreemap.rs │ │ ├── hashmap.rs │ │ ├── vec.rs │ │ └── tuple.rs │ ├── deser │ │ ├── instant.rs │ │ ├── btreemap.rs │ │ ├── hashset.rs │ │ ├── hashmap.rs │ │ ├── mutex.rs │ │ ├── refcell.rs │ │ ├── derefs.rs │ │ ├── vec.rs │ │ ├── tuple.rs │ │ └── basic.rs │ ├── tokens │ │ ├── parse.pest │ │ └── mod.rs │ ├── deser.rs │ ├── lib.rs │ ├── access.rs │ ├── root.rs │ ├── reflector.rs │ └── climber.rs ├── tests │ ├── common │ │ ├── random.rs │ │ ├── pseudo_mutex.rs │ │ └── mod.rs │ └── integ.rs └── Cargo.toml ├── .gitignore ├── interact_derive ├── README.md ├── LICENSE-MIT ├── crates-io.md ├── LICENSE-APACHE ├── .gitignore └── Cargo.toml ├── interact_prompt ├── README.md ├── .gitignore ├── LICENSE-MIT ├── crates-io.md ├── LICENSE-APACHE ├── examples │ ├── common │ │ ├── mod.rs │ │ ├── random.rs │ │ └── pseudo_mutex.rs │ ├── mini-example.rs │ └── large-example.rs ├── Cargo.toml └── src │ ├── registry.rs │ ├── print.rs │ └── lib.rs ├── rustfmt.toml ├── .travis.yml ├── crates-io.md ├── Cargo.toml ├── LICENSE-MIT ├── README.md └── LICENSE-APACHE /doc/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /interact/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /interact/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | /target/ 3 | -------------------------------------------------------------------------------- /interact/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /interact/crates-io.md: -------------------------------------------------------------------------------- 1 | ../crates-io.md -------------------------------------------------------------------------------- /interact_derive/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /interact_prompt/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /interact/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /interact_derive/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /interact_derive/crates-io.md: -------------------------------------------------------------------------------- 1 | ../crates-io.md -------------------------------------------------------------------------------- /interact_prompt/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /interact_prompt/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /interact_prompt/crates-io.md: -------------------------------------------------------------------------------- 1 | ../crates-io.md -------------------------------------------------------------------------------- /interact_derive/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /interact_prompt/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /interact_derive/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target 3 | **/*.rs.bk 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /interact_prompt/examples/common/mod.rs: -------------------------------------------------------------------------------- 1 | ../../../interact/tests/common/mod.rs -------------------------------------------------------------------------------- /doc/demo.apng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interact-rs/interact/HEAD/doc/demo.apng -------------------------------------------------------------------------------- /interact_prompt/examples/common/random.rs: -------------------------------------------------------------------------------- 1 | ../../../interact/tests/common/random.rs -------------------------------------------------------------------------------- /interact/src/util/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod assist; 2 | pub mod expect; 3 | pub mod node_tree; 4 | -------------------------------------------------------------------------------- /interact_prompt/examples/common/pseudo_mutex.rs: -------------------------------------------------------------------------------- 1 | ../../../interact/tests/common/pseudo_mutex.rs -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | error_on_line_overflow = true 2 | error_on_unformatted = true 3 | edition = "2018" 4 | -------------------------------------------------------------------------------- /doc/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Dan Aloni"] 3 | multilingual = false 4 | src = "src" 5 | title = "Interact" 6 | -------------------------------------------------------------------------------- /interact/src/access/iter.rs: -------------------------------------------------------------------------------- 1 | pub trait ReflectIter { 2 | fn reflect_next(&mut self) -> Option; 3 | } 4 | -------------------------------------------------------------------------------- /interact/src/deser/instant.rs: -------------------------------------------------------------------------------- 1 | use std::time::Instant; 2 | 3 | use crate::deser::Deser; 4 | 5 | // TODO: implement 6 | 7 | impl Deser for Instant {} 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | matrix: 7 | allow_failures: 8 | - rust: beta 9 | - rust: nightly 10 | fast_finish: true 11 | -------------------------------------------------------------------------------- /doc/src/examples.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | In the section Interact is demoed using modifications of existing Rust programs. 4 | 5 | * [Actix chat server](examples/actix.md) 6 | * [Alacritty](examples/alacritty.md) 7 | 8 | -------------------------------------------------------------------------------- /doc/src/using-prompt.md: -------------------------------------------------------------------------------- 1 | # Using the prompt 2 | 3 | The Interact prompt lets the user probe the registered data, and possibly to modify it to some degree. 4 | 5 | This section provides various examples for what is possible at the prompt. 6 | -------------------------------------------------------------------------------- /crates-io.md: -------------------------------------------------------------------------------- 1 | **Interact is a framework for friendly online introspection of the running program state in an intuitive command-line *interact*ive way.** 2 | 3 | Please go to the [homepage](https://github.com/interact-rs/interact) for more details. 4 | -------------------------------------------------------------------------------- /interact/src/deser/btreemap.rs: -------------------------------------------------------------------------------- 1 | use crate::deser::Deser; 2 | use std::collections::BTreeMap; 3 | 4 | // TODO: implement 5 | 6 | impl Deser for BTreeMap 7 | where 8 | K: Eq + Ord + Deser, 9 | V: Deser, 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "interact", 5 | "interact_derive", 6 | "interact_prompt", 7 | ] 8 | 9 | [patch.crates-io] 10 | interact = { path = "interact" } 11 | interact_derive = { path = "interact_derive" } 12 | interact_prompt = { path = "interact_prompt" } 13 | -------------------------------------------------------------------------------- /interact/src/deser/hashset.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::hash::BuildHasher; 3 | use std::hash::Hash; 4 | 5 | use crate::deser::Deser; 6 | 7 | // TODO: implement 8 | 9 | impl Deser for HashSet 10 | where 11 | V: Eq + Hash + Deser, 12 | S: BuildHasher, 13 | { 14 | } 15 | -------------------------------------------------------------------------------- /interact/src/deser/hashmap.rs: -------------------------------------------------------------------------------- 1 | use crate::deser::Deser; 2 | 3 | use std::collections::HashMap; 4 | use std::hash::{BuildHasher, Hash}; 5 | 6 | // TODO: implement 7 | 8 | impl Deser for HashMap 9 | where 10 | K: Eq + Hash + Deser, 11 | V: Deser, 12 | S: BuildHasher, 13 | { 14 | } 15 | -------------------------------------------------------------------------------- /interact/src/deser/mutex.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Mutex; 2 | 3 | use crate::deser::{Deser, Result, Tracker}; 4 | 5 | impl Deser for Mutex 6 | where 7 | T: Deser, 8 | { 9 | fn deser<'a, 'b>(tracker: &mut Tracker<'a, 'b>) -> Result { 10 | Ok(Mutex::new(T::deser(tracker)?)) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /interact/src/access/explicit.rs: -------------------------------------------------------------------------------- 1 | use interact_derive::derive_interact_prelude; 2 | 3 | derive_interact_prelude! { 4 | enum Option { 5 | None, 6 | Some(T), 7 | } 8 | } 9 | 10 | derive_interact_prelude! { 11 | enum Result { 12 | Ok(T), 13 | Err(E), 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /interact/src/deser/refcell.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | use crate::deser::{Deser, Result, Tracker}; 4 | 5 | impl Deser for RefCell 6 | where 7 | T: Deser, 8 | { 9 | fn deser<'a, 'b>(tracker: &mut Tracker<'a, 'b>) -> Result { 10 | Ok(RefCell::new(T::deser(tracker)?)) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /interact/tests/common/random.rs: -------------------------------------------------------------------------------- 1 | use rand::distributions::Distribution; 2 | use rand::distributions::Standard; 3 | use rand::Rng; 4 | 5 | pub trait Rand { 6 | fn new_random(rng: &mut R) -> Self; 7 | } 8 | 9 | impl Rand for T 10 | where 11 | Standard: Distribution, 12 | { 13 | fn new_random(rng: &mut R) -> Self { 14 | rng.gen() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /doc/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [Overview](README.md) 4 | - [Examples](examples.md) 5 | - [Actix chat server](examples/actix.md) 6 | - [Alacritty](examples/alacritty.md) 7 | - [Using derive](using-derive.md) 8 | - [Attributes](attributes.md) 9 | - [Registering data](registering-data.md) 10 | - [Using the prompt](using-prompt.md) 11 | - [Accessing data](access-data.md) 12 | - [Modifying data](modify-data.md) 13 | - [Calling methods](calling-methods.md) 14 | -------------------------------------------------------------------------------- /doc/src/using-derive.md: -------------------------------------------------------------------------------- 1 | # Using derive 2 | 3 | ## Cargo.toml 4 | 5 | The Interact dependency is needed: 6 | 7 | ```toml 8 | [dependencies] 9 | interact = "0.3" 10 | ``` 11 | 12 | ## Source 13 | 14 | * The a crate's top level, `extern crate interact` is needed. 15 | * At places `#[derive(Interact)]` is needed, import the needed proc macro: `use interact::Interact;` 16 | 17 | An example: 18 | 19 | ```rust 20 | extern crate interact; 21 | 22 | use interact::Interact; 23 | 24 | #[derive(Interact)] 25 | struct Point { 26 | x: i32, 27 | y: i32, 28 | } 29 | ``` 30 | -------------------------------------------------------------------------------- /doc/src/calling-methods.md: -------------------------------------------------------------------------------- 1 | # Calling methods 2 | 3 | By specifying Interact's special `mut_fn` or `immut_fn` [container attributes](attributes.html#container-attributes), you can add methods that would be reachable from the Interact prompt, upon reaching values that match the types on which the special methods are defined. 4 | 5 | For example, given the following type: 6 | 7 | ```rust,ignore 8 | #[derive(Interact)] 9 | #[interact(mut_fn(add(param_a)))] 10 | struct Baz(u32); 11 | 12 | impl Baz { 13 | fn add(&mut self, param_a: u32) { 14 | self.0 += param_a; 15 | } 16 | } 17 | ``` 18 | 19 | We can call the `add` methods: 20 | 21 | ```rust,ignore 22 | >>> state.baz_val 23 | Baz (1) 24 | 25 | >>> state.baz_val.add(3) 26 | >>> state.baz_val 27 | Baz (4) 28 | ``` 29 | -------------------------------------------------------------------------------- /doc/src/registering-data.md: -------------------------------------------------------------------------------- 1 | # Registering data 2 | 3 | The `interact_prompt` crate provides helpers for registering data to be examined. Currently, data must be owned, so if it is shared by threads running in the background in parallel to the prompt, it needs to be provided using `Arc<_>`. If the data is expeced to be shared _and_ mutable by the Interact prompt, then it should be wrapped in `Arc>`. 4 | 5 | For example: 6 | 7 | ```rust,ignore 8 | use interact_prompt::{SendRegistry}; 9 | 10 | fn register_global_mutable_data(global_state: Arc>) { 11 | SendRegistry::insert("global", Box::new(global_state)); 12 | } 13 | 14 | fn register_global_readable_data(readonly: Arc) { 15 | SendRegistry::insert("readonly", Box::new(readonly)); 16 | } 17 | ``` 18 | -------------------------------------------------------------------------------- /interact/src/deser/derefs.rs: -------------------------------------------------------------------------------- 1 | use crate::deser::{Deser, Result, Tracker}; 2 | use std::rc::Rc; 3 | use std::sync::Arc; 4 | 5 | impl<'a, T: 'a> Deser for &'a T where T: Deser {} 6 | 7 | impl<'a, T: 'a> Deser for &'a mut T where T: Deser {} 8 | 9 | impl Deser for Box 10 | where 11 | T: Deser, 12 | { 13 | fn deser<'a, 'b>(tracker: &mut Tracker<'a, 'b>) -> Result { 14 | Ok(Box::new(T::deser(tracker)?)) 15 | } 16 | } 17 | 18 | impl Deser for Rc 19 | where 20 | T: Deser, 21 | { 22 | fn deser<'a, 'b>(tracker: &mut Tracker<'a, 'b>) -> Result { 23 | Ok(Rc::new(T::deser(tracker)?)) 24 | } 25 | } 26 | 27 | impl Deser for Arc 28 | where 29 | T: Deser, 30 | { 31 | fn deser<'a, 'b>(tracker: &mut Tracker<'a, 'b>) -> Result { 32 | Ok(Arc::new(T::deser(tracker)?)) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /interact_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "interact_derive" 3 | version = "0.3.6" 4 | authors = ["Dan Aloni "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | description = "A framework for online program state introspection" 8 | homepage = "https://github.com/interact-rs/interact" 9 | repository = "https://github.com/interact-rs/interact" 10 | documentation = "https://interact-rs.github.io/interact/book" 11 | keywords = ["prompt", "reflection", "introspection", "interact", "cli"] 12 | categories = ["command-line-interface"] 13 | readme = "crates-io.md" 14 | include = ["Cargo.toml", "src/**/*.rs", "crates-io.md", "README.md", "LICENSE-APACHE", "LICENSE-MIT"] 15 | 16 | [lib] 17 | proc-macro = true 18 | 19 | [dependencies] 20 | syn = { version = "0.15", features = ["extra-traits", "full"] } 21 | quote = "0.6" 22 | proc-macro2 = "0.4" 23 | -------------------------------------------------------------------------------- /interact/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "interact" 3 | version = "0.3.6" 4 | authors = ["Dan Aloni "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | description = "A framework for online program state introspection" 8 | homepage = "https://github.com/interact-rs/interact" 9 | repository = "https://github.com/interact-rs/interact" 10 | documentation = "https://interact-rs.github.io/interact/book" 11 | keywords = ["prompt", "reflection", "introspection", "interact", "cli"] 12 | categories = ["command-line-interface"] 13 | readme = "crates-io.md" 14 | include = ["Cargo.toml", "src/**/*.rs", "src/**/*.pest", "tests/**/*.rs", "crates-io.md", "README.md", "LICENSE-APACHE", "LICENSE-MIT"] 15 | 16 | [dependencies] 17 | interact_derive = "0.3.6" 18 | ron = "0.4" 19 | pest = "2.0.1" 20 | pest_derive = "2.0.1" 21 | 22 | [dev-dependencies] 23 | rand = "=0.5" 24 | pretty_assertions = "0.5.1" 25 | -------------------------------------------------------------------------------- /interact_prompt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "interact_prompt" 3 | version = "0.3.6" 4 | authors = ["Dan Aloni "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | description = "A framework for online program state introspection" 8 | homepage = "https://github.com/interact-rs/interact" 9 | repository = "https://github.com/interact-rs/interact" 10 | documentation = "https://interact-rs.github.io/interact/book" 11 | keywords = ["prompt", "reflection", "introspection", "interact", "cli"] 12 | categories = ["command-line-interface"] 13 | readme = "crates-io.md" 14 | include = ["Cargo.toml", "src/**/*.rs", "crates-io.md", "README.md", "LICENSE-APACHE", "LICENSE-MIT"] 15 | 16 | [dependencies] 17 | rustyline = "6.1" 18 | interact = "0.3.6" 19 | lazy_static = "1.2" 20 | ansi_term = "0.11" 21 | 22 | [dev-dependencies] 23 | structopt = "0.2" 24 | structopt-derive = "0.2" 25 | rand = "=0.5" 26 | -------------------------------------------------------------------------------- /interact_prompt/examples/mini-example.rs: -------------------------------------------------------------------------------- 1 | extern crate interact; 2 | 3 | use interact::Interact; 4 | use interact_prompt::{LocalRegistry, Settings}; 5 | use std::{cell::RefCell, rc::Rc}; 6 | 7 | #[derive(Interact)] 8 | struct Point { 9 | x: i32, 10 | y: i32, 11 | } 12 | 13 | #[derive(Interact)] 14 | struct State { 15 | maybe_point: Option, 16 | complex: ((((usize, usize), u32, (u32, (u32,))), u32), u32), 17 | behind_rc: Rc>, 18 | behind_rc2: Rc>, 19 | } 20 | 21 | fn main() -> Result<(), interact_prompt::PromptError> { 22 | let rc = Rc::new(RefCell::new(3)); 23 | let state = State { 24 | maybe_point: Some(Point { x: 3, y: 3 }), 25 | complex: ((((0, 0), 0, (0, (0,))), 0), 0), 26 | behind_rc: rc.clone(), 27 | behind_rc2: rc, 28 | }; 29 | 30 | LocalRegistry::insert("state", Box::new(state)); 31 | interact_prompt::direct(Settings::default(), ())?; 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /doc/src/attributes.md: -------------------------------------------------------------------------------- 1 | # Attributes 2 | 3 | ## Container attributes 4 | 5 | Following `#[derive(Interact)]`, methods to be called from the prompt can be 6 | specified by name, along with their parameters, and whether they take in `&self` 7 | or `&mut self`. 8 | 9 | ```rust,ignore 10 | #[interact(mut_fn(function_name(param_a, param_b))) 11 | ``` 12 | 13 | ```rust,ignore 14 | #[interact(immut_fn(function_name(param_a, param_b))) 15 | ``` 16 | 17 | For example: 18 | 19 | ```rust 20 | #[derive(Interact)] 21 | #[interact(mut_fn(add(param_a)))] 22 | struct Baz(u32); 23 | 24 | impl Baz { 25 | fn add(&mut self, param_a: u32) { 26 | self.0 += param_a; 27 | } 28 | } 29 | ``` 30 | 31 | ## Field attributes 32 | 33 | The `skip` attribute allows to make some fields invisible: 34 | ```rust,ignore 35 | #[interact(skip)) 36 | ``` 37 | 38 | The downside is that having any skipped field on a type means that it is 39 | unbuildable, and therefore cannot be passed as value to functions or to be 40 | assigned using `=` in an expression. 41 | -------------------------------------------------------------------------------- /interact/src/access/instant.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::sync::Arc; 3 | use std::time::Instant; 4 | 5 | use crate::access::ReflectDirect; 6 | use crate::climber::{ClimbError, Climber}; 7 | use crate::node_tree::{NodeInfo, NodeTree}; 8 | use crate::reflector::Reflector; 9 | 10 | impl ReflectDirect for Instant { 11 | fn immut_reflector(&self, reflector: &Arc) -> NodeTree { 12 | let meta = try_seen_dyn!(self, reflector); 13 | NodeInfo::Leaf(Cow::Owned(format!("{:?}", self.elapsed()))).with_meta(meta) 14 | } 15 | 16 | fn immut_climber<'a>( 17 | &self, 18 | _climber: &mut Climber<'a>, 19 | ) -> Result, ClimbError> { 20 | return Ok(None); 21 | } 22 | 23 | fn mut_climber<'a>( 24 | &mut self, 25 | _climber: &mut Climber<'a>, 26 | ) -> Result, ClimbError> { 27 | return Ok(None); 28 | } 29 | } 30 | 31 | use interact_derive::derive_interact_opaque; 32 | 33 | derive_interact_opaque! { 34 | struct Instant; 35 | } 36 | -------------------------------------------------------------------------------- /interact/src/access/derive.rs: -------------------------------------------------------------------------------- 1 | use super::Access; 2 | 3 | #[derive(Debug, Eq, PartialEq)] 4 | pub enum StructKind { 5 | Unit, 6 | Tuple(usize), 7 | Fields(&'static [&'static str]), 8 | } 9 | 10 | #[derive(Debug, Eq, PartialEq)] 11 | pub struct Struct { 12 | pub name: &'static str, 13 | pub kind: StructKind, 14 | } 15 | 16 | #[derive(Debug)] 17 | pub struct Enum { 18 | pub name: &'static str, 19 | pub opts: &'static [&'static str], 20 | } 21 | 22 | pub trait ReflectStruct { 23 | fn get_desc(&self) -> Struct; 24 | fn get_field_by_name(&self, name: &'static str) -> Option<&dyn Access>; 25 | fn get_field_by_idx(&self, idx: usize) -> Option<&dyn Access>; 26 | fn get_field_by_name_mut(&mut self, name: &'static str) -> Option<&mut dyn Access>; 27 | fn get_field_by_idx_mut(&mut self, idx: usize) -> Option<&mut dyn Access>; 28 | } 29 | 30 | pub trait ReflectEnum { 31 | fn get_variant_desc(&self) -> Enum; 32 | fn get_variant_struct(&self) -> &dyn ReflectStruct; 33 | fn get_variant_struct_mut(&mut self) -> &mut dyn ReflectStruct; 34 | } 35 | -------------------------------------------------------------------------------- /interact/src/access/mutex.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use std::sync::Mutex; 3 | 4 | use crate::access::{Access, ReflectDirect}; 5 | use crate::climber::{ClimbError, Climber}; 6 | use crate::node_tree::{NodeInfo, NodeTree}; 7 | use crate::reflector::Reflector; 8 | 9 | impl ReflectDirect for Mutex 10 | where 11 | T: Access, 12 | { 13 | fn immut_reflector(&self, reflector: &Arc) -> NodeTree { 14 | match self.try_lock() { 15 | Ok(locked) => Reflector::reflect(reflector, &*locked), 16 | Err(_) => NodeInfo::Locked.into_node(), 17 | } 18 | } 19 | 20 | fn immut_climber<'a>(&self, climber: &mut Climber<'a>) -> Result, ClimbError> { 21 | climber.mutex_handling(self).map(Some) 22 | } 23 | 24 | fn mut_climber<'a>( 25 | &mut self, 26 | climber: &mut Climber<'a>, 27 | ) -> Result, ClimbError> { 28 | climber.mutex_handling(self).map(Some) 29 | } 30 | } 31 | 32 | use interact_derive::derive_interact_opaque; 33 | 34 | derive_interact_opaque! { 35 | struct Mutex; 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /interact/src/access/refcell.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::sync::Arc; 3 | 4 | use crate::access::{Access, ReflectDirect}; 5 | use crate::climber::{ClimbError, Climber}; 6 | use crate::node_tree::{NodeInfo, NodeTree}; 7 | use crate::reflector::Reflector; 8 | 9 | impl ReflectDirect for RefCell 10 | where 11 | T: Access, 12 | { 13 | fn immut_reflector(&self, reflector: &Arc) -> NodeTree { 14 | match self.try_borrow() { 15 | Ok(borrowed) => Reflector::reflect(reflector, &*borrowed), 16 | Err(_) => NodeInfo::BorrowedMut.into_node(), 17 | } 18 | } 19 | 20 | fn immut_climber<'a>(&self, climber: &mut Climber<'a>) -> Result, ClimbError> { 21 | climber.refcell_handling(self).map(Some) 22 | } 23 | 24 | fn mut_climber<'a>( 25 | &mut self, 26 | climber: &mut Climber<'a>, 27 | ) -> Result, ClimbError> { 28 | climber.refcell_handling(self).map(Some) 29 | } 30 | } 31 | 32 | use interact_derive::derive_interact_opaque; 33 | 34 | derive_interact_opaque! { 35 | #[interact(mut_assign)] 36 | struct RefCell; 37 | } 38 | -------------------------------------------------------------------------------- /doc/src/modify-data.md: -------------------------------------------------------------------------------- 1 | # Modifying data 2 | 3 | Types that expose a mutable interface, for example via `Arc>`, can have their fields be assigned and modified from the Interact prompt. 4 | 5 | Interact knows the basic types, and is also able to construct values of derived types for which the `#[interact(skip)]` attribute was _not_ used for any field. 6 | 7 | ## Assignments 8 | 9 | Assignments are done using `=` at the prompt. 10 | 11 | For example, check `cargo run --example large-example`: 12 | 13 | ```rust,ignore 14 | >>> complex.tuple 15 | ((690498389, VarUnit, (193, 38)), 1262478744) 16 | 17 | >>> complex.tuple.0.2 18 | (193, 38) 19 | 20 | >>> complex.tuple.0.2 = (1, 1) 21 | >>> complex.tuple 22 | ((690498389, VarUnit, (1, 1)), 1262478744) 23 | 24 | >>> complex.tuple.0.1 = VarNamed { a: 3, b: 10} 25 | >>> complex.tuple 26 | ((690498389, VarNamed { a: 3, b: 10 }, (1, 1)), 1262478744) 27 | ``` 28 | 29 | ## Wrapper types 30 | 31 | The wrapper types `Rc`, `RefCell`, `Mutex`, `Box` are transparent to construction of values, and need not be specified. 32 | 33 | ```rust,ignore 34 | >>> complex.boxed = VarNamed { a: 3, b: 10} 35 | 36 | >>> complex.boxed 37 | VarNamed { a: 3, b: 10 } 38 | ``` 39 | -------------------------------------------------------------------------------- /interact/src/deser/vec.rs: -------------------------------------------------------------------------------- 1 | use crate::deser::Deser; 2 | 3 | impl Deser for &[T] where T: Deser {} 4 | 5 | impl Deser for &mut [T] where T: Deser {} 6 | 7 | // TODO: implement 8 | impl Deser for Vec where T: Deser {} 9 | 10 | macro_rules! sized_iter { 11 | ($t:ty) => { 12 | // TODO: implement 13 | impl Deser for $t where T: Deser {} 14 | }; 15 | } 16 | 17 | sized_iter!([T; 1]); 18 | sized_iter!([T; 2]); 19 | sized_iter!([T; 3]); 20 | sized_iter!([T; 4]); 21 | sized_iter!([T; 5]); 22 | sized_iter!([T; 6]); 23 | sized_iter!([T; 7]); 24 | sized_iter!([T; 8]); 25 | sized_iter!([T; 9]); 26 | sized_iter!([T; 10]); 27 | sized_iter!([T; 11]); 28 | sized_iter!([T; 12]); 29 | sized_iter!([T; 13]); 30 | sized_iter!([T; 14]); 31 | sized_iter!([T; 15]); 32 | sized_iter!([T; 16]); 33 | sized_iter!([T; 17]); 34 | sized_iter!([T; 18]); 35 | sized_iter!([T; 19]); 36 | sized_iter!([T; 20]); 37 | sized_iter!([T; 21]); 38 | sized_iter!([T; 22]); 39 | sized_iter!([T; 23]); 40 | sized_iter!([T; 24]); 41 | sized_iter!([T; 25]); 42 | sized_iter!([T; 26]); 43 | sized_iter!([T; 27]); 44 | sized_iter!([T; 28]); 45 | sized_iter!([T; 29]); 46 | sized_iter!([T; 30]); 47 | sized_iter!([T; 31]); 48 | sized_iter!([T; 32]); 49 | -------------------------------------------------------------------------------- /interact_prompt/examples/large-example.rs: -------------------------------------------------------------------------------- 1 | extern crate interact; 2 | extern crate structopt_derive; 3 | 4 | use interact_prompt::{LocalRegistry, SendRegistry, Settings}; 5 | 6 | mod common; 7 | use common::Rand; 8 | 9 | use structopt::clap::AppSettings; 10 | use structopt::StructOpt; 11 | 12 | #[derive(StructOpt, Debug)] 13 | #[structopt(raw( 14 | global_settings = "&[AppSettings::ColoredHelp, AppSettings::VersionlessSubcommands]" 15 | ))] 16 | pub struct Opt { 17 | #[structopt(short = "i", long = "initial-command")] 18 | initial_command: Option, 19 | 20 | #[structopt(short = "h", long = "history-file")] 21 | history_file: Option, 22 | } 23 | 24 | fn main() -> Result<(), interact_prompt::PromptError> { 25 | let seed = 42; 26 | let mut rng: rand::StdRng = rand::SeedableRng::seed_from_u64(seed); 27 | 28 | use common::{Basic, Complex, LocalRcLoop}; 29 | 30 | SendRegistry::insert("complex", Box::new(Complex::new_random(&mut rng))); 31 | SendRegistry::insert("basic", Box::new(Basic::new_random(&mut rng))); 32 | LocalRegistry::insert("rc_loops", Box::new(LocalRcLoop::new_random(&mut rng))); 33 | 34 | let Opt { 35 | history_file, 36 | initial_command, 37 | } = Opt::from_args(); 38 | 39 | interact_prompt::direct( 40 | Settings { 41 | initial_command, 42 | history_file, 43 | }, 44 | (), 45 | )?; 46 | Ok(()) 47 | } 48 | -------------------------------------------------------------------------------- /doc/src/access-data.md: -------------------------------------------------------------------------------- 1 | # Accessing data 2 | 3 | ## Whole state 4 | 5 | Suppose a value of the following simple state is registered: 6 | 7 | ```rust 8 | #[derive(Interact)] 9 | struct Point { 10 | x: u32, 11 | y: u32, 12 | } 13 | ``` 14 | 15 | The whole of it can be printed, and the result is similar to a pretty printed `Debug`: 16 | 17 | ```shell 18 | >>> state 19 | Point { x: 3, y: 4 } 20 | ``` 21 | 22 | Tuple structs are accessed similarly using Rust's `.0`, `.1`, etc. 23 | 24 | ## Field access 25 | 26 | The syntax for field access is similar to Rust's. For example, accessing one of the fields of the previous example: 27 | 28 | ```shell 29 | >>> state.x 30 | 3 31 | ``` 32 | 33 | ## Enum access 34 | 35 | ```rust 36 | struct OptPoint { 37 | x: Option, 38 | y: Option, 39 | } 40 | ``` 41 | 42 | Suppose that we have an instance of this struct with the following value: 43 | 44 | `OptPoint { x: None, y: Some(3) }` 45 | 46 | Unlike in Rust, we can have a full path to the variant's value through variant's name: 47 | 48 | ```shell 49 | >>> state.y.Some 50 | (3) 51 | >>> state.y.Some.0 52 | 3 53 | >>> state.x 54 | None 55 | >>> basic.x.None 56 | None 57 | ``` 58 | 59 | ## Vec, HashMap, and BTreeMap access 60 | 61 | Accessing vectors and maps are done like you'd expected via `[]`. Currently, ranges are _not_ supported in vectors and sorted maps. 62 | 63 | ## Access via `Mutex`, `Rc`, `Arc`, `RefCell`, `Box` 64 | 65 | Interact elides complexity to access paths when wrapper types are used. For Mutex, it uses `.try_lock()` behind the scenes. For `RefCell` it uses `try_borrow()`. 66 | -------------------------------------------------------------------------------- /interact/src/access/hashset.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::hash::BuildHasher; 3 | use std::hash::Hash; 4 | use std::sync::Arc; 5 | 6 | use crate::access::{iter::ReflectIter, Access, ReflectDirect}; 7 | use crate::climber::{ClimbError, Climber}; 8 | use crate::node_tree::NodeTree; 9 | use crate::reflector::Reflector; 10 | 11 | impl<'a, K> ReflectIter<&'a dyn Access> for std::collections::hash_set::Iter<'a, K> 12 | where 13 | K: Eq + Hash + Access, 14 | { 15 | fn reflect_next(&mut self) -> Option<&'a dyn Access> { 16 | match self.next() { 17 | None => None, 18 | Some(value) => Some(value), 19 | } 20 | } 21 | } 22 | 23 | impl ReflectDirect for HashSet 24 | where 25 | K: Eq + Hash + Access, 26 | S: BuildHasher, 27 | { 28 | fn immut_reflector(&self, reflector: &Arc) -> NodeTree { 29 | let mut i = Box::new(self.iter()); 30 | Reflector::reflect_set(reflector, &mut *i, "HashSet") 31 | } 32 | 33 | fn immut_climber<'a>( 34 | &self, 35 | _climber: &mut Climber<'a>, 36 | ) -> Result, ClimbError> { 37 | return Ok(None); 38 | } 39 | 40 | fn mut_climber<'a>( 41 | &mut self, 42 | _climber: &mut Climber<'a>, 43 | ) -> Result, ClimbError> { 44 | return Ok(None); 45 | } 46 | } 47 | 48 | use interact_derive::derive_interact_opaque; 49 | 50 | derive_interact_opaque! { 51 | #[interact(skip_bound(S))] 52 | #[interact(immut_fn(len()))] 53 | struct HashSet 54 | where 55 | K: Eq + std::hash::Hash, 56 | S: std::hash::BuildHasher; 57 | } 58 | -------------------------------------------------------------------------------- /interact/src/access/basic.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::sync::Arc; 3 | 4 | use crate::access::ReflectDirect; 5 | use crate::climber::{ClimbError, Climber}; 6 | use crate::node_tree::{NodeInfo, NodeTree}; 7 | use crate::reflector::Reflector; 8 | 9 | use interact_derive::derive_interact_basic; 10 | 11 | macro_rules! simple { 12 | ($a:tt, $fmt:expr) => { 13 | derive_interact_basic! { 14 | #[interact(mut_assign)] 15 | struct $a; 16 | } 17 | 18 | impl ReflectDirect for $a { 19 | fn immut_reflector(&self, reflector: &Arc) -> NodeTree { 20 | let obj_ptr = ((self as *const _) as usize, 0); 21 | let meta = match Reflector::seen_ptr(reflector, obj_ptr) { 22 | Ok(v) => return v, 23 | Err(meta) => meta, 24 | }; 25 | NodeInfo::Leaf(Cow::Owned(format!($fmt, self))).with_meta(meta) 26 | } 27 | 28 | fn immut_climber<'a>( 29 | &self, 30 | _climber: &mut Climber<'a>, 31 | ) -> Result, ClimbError> { 32 | Ok(None) 33 | } 34 | 35 | fn mut_climber<'a>( 36 | &mut self, 37 | _climber: &mut Climber<'a>, 38 | ) -> Result, ClimbError> { 39 | Ok(None) 40 | } 41 | } 42 | }; 43 | } 44 | 45 | simple!(usize, "{}"); 46 | simple!(u64, "{}"); 47 | simple!(u32, "{}"); 48 | simple!(u16, "{}"); 49 | simple!(u8, "{}"); 50 | simple!(isize, "{}"); 51 | simple!(bool, "{}"); 52 | simple!(String, "{:?}"); 53 | simple!(char, "{:?}"); 54 | simple!(i64, "{}"); 55 | simple!(i32, "{}"); 56 | simple!(i16, "{}"); 57 | simple!(i8, "{}"); 58 | -------------------------------------------------------------------------------- /doc/src/README.md: -------------------------------------------------------------------------------- 1 | # Interact 2 | 3 | [Interact](https://github.com/interact-rs/interact) is a Rust framework for friendly online introspection of the running program state in an intuitive command-line *interact*ive way. 4 | 5 | Interact is useful for server programs that otherwise receive no input. You can use Interact to make your server receive commands using the special prompt from the `interact_prompt` crate. The commands can be used to browse your server's internal state, modify it, and call method functions that were specified in `interact` derive attributes. 6 | 7 | ## Reference 8 | 9 | * Crate docs of latest release: 10 | * [interact](https://docs.rs/interact) ![x](https://img.shields.io/crates/v/interact.svg) 11 | * [interact_prompt](https://docs.rs/interact_prompt) ![x](https://img.shields.io/crates/v/interact_prompt.svg) 12 | * [interact_derive](https://docs.rs/interact_derive) ![x](https://img.shields.io/crates/v/interact_derive.svg) 13 | 14 | * Crate docs of master (possibly unreleased) version: 15 | * [interact](https://interact-rs.github.io/interact/doc/interact/index.html) 16 | * [interact_prompt](https://interact-rs.github.io/interact/doc/interact_prompt/index.html) 17 | * [interact_derive](https://interact-rs.github.io/interact/doc/interact_derive/index.html) 18 | 19 | ## Design 20 | 21 | Using two traits, `Access` and `Deser`, Interact exposes types as trait objects, similarly to the `Any` trait, but with a functionality of reflection. The `Access` trait allows to probe the fields of structs and enums and modify them. It also allows to iterate arrays, vectors, and maps. It also allows to safely punch through `Rc`, `Arc`, and `Mutex`. The traits can be derived using `#[derive(Interact)]`. 22 | 23 | At the prompt side, predictive parsing is used for providing full auto-complete and hinting, while constructing access paths, and while constructing values used in field assignments and function calls. 24 | -------------------------------------------------------------------------------- /doc/src/examples/alacritty.md: -------------------------------------------------------------------------------- 1 | # Alacritty 2 | 3 | By enabling Interact for a program such as Alacritty, we can probe and modify its state while it runs (for example, modify the cursor's position from the Interact prompt). 4 | 5 | ### Summary of changes 6 | 7 | The [changes in Alacritty](https://github.com/interact-rs/alacritty/compare/base...interact-rs:interact-demo) do the following: 8 | 9 | * Add an invocation of the Interact prompt. 10 | * Add `#[derive(Interact)]` for a small portion of the types. 11 | * Add special `Access` and `Deser` deriving for the `FairMutex` type. 12 | 13 | ## Demo 14 | 15 | Here is the interactive state it produces: 16 | 17 | ```rust.ignore 18 | >>> term 19 | Term { 20 | grid: Grid { 21 | cols: Column (80), 22 | lines: Line (24), 23 | display_offset: 0, 24 | scroll_limit: 0, 25 | max_scroll_limit: 100000 26 | }, 27 | input_needs_wrap: false, 28 | next_title: None, 29 | next_mouse_cursor: None, 30 | alt_grid: Grid { 31 | cols: Column (80), 32 | lines: Line (24), 33 | display_offset: 0, 34 | scroll_limit: 0, 35 | max_scroll_limit: 0 36 | }, 37 | alt: false, 38 | cursor: Cursor { 39 | point: Point { line: Line (5), col: Column (45) }, 40 | template: Cell { c: ' ' }, 41 | charsets: Charsets ([ Ascii, Ascii, Ascii, Ascii ]) 42 | }, 43 | dirty: false, 44 | next_is_urgent: None, 45 | cursor_save: Cursor { 46 | point: Point { line: Line (0), col: Column (0) }, 47 | template: Cell { c: ' ' }, 48 | charsets: Charsets ([ Ascii, Ascii, Ascii, Ascii ]) 49 | }, 50 | cursor_save_alt: Cursor { 51 | point: Point { line: Line (0), col: Column (0) }, 52 | template: Cell { c: ' ' }, 53 | charsets: Charsets ([ Ascii, Ascii, Ascii, Ascii ]) 54 | }, 55 | semantic_escape_chars: ",│`|:\"\' ()[]{}<>", 56 | dynamic_title: true, 57 | tabspaces: 8, 58 | auto_scroll: false 59 | } 60 | ``` 61 | -------------------------------------------------------------------------------- /interact/src/tokens/parse.pest: -------------------------------------------------------------------------------- 1 | token_list = _{ (token)* } 2 | 3 | token = _{ 4 | identifier 5 | | nonnegative_decimal 6 | | decimal 7 | | string_literal 8 | | char_literal 9 | | range_access 10 | | range_access_inclusive 11 | | field_access 12 | | subscript_open 13 | | subscript_close 14 | | tuple_open 15 | | tuple_close 16 | | curly_open 17 | | curly_close 18 | | asterix 19 | | colon 20 | | assign 21 | | comma 22 | | invalid 23 | } 24 | 25 | identifier = @{ (alpha | underscore) ~ (alphanumeric | underscore)* } 26 | decimal = @{ nonnegative_decimal | negative_decimal } 27 | nonnegative_decimal = @{ (nonzero ~ digit*) | digit } 28 | string_literal = @{ "\"" ~ literal_char* ~ "\"" } 29 | char_literal = @{ "\'" ~ single_literal_char ~ "\'" } 30 | 31 | field_access = { "." } 32 | range_access = { ".." } 33 | assign = { "=" } 34 | range_access_inclusive = { ".=" } 35 | 36 | subscript_open = { "[" } 37 | subscript_close = { "]" } 38 | 39 | asterix = { "*" } 40 | 41 | curly_open = { "{" } 42 | curly_close = { "}" } 43 | 44 | tuple_open = { "(" } 45 | tuple_close = { ")" } 46 | colon = { ":" } 47 | 48 | comma = { "," } 49 | underscore = { "_" } 50 | 51 | invalid = { ANY } 52 | 53 | literal_char = _{ escape_sequence | (!"\"" ~ ANY) } 54 | single_literal_char = _{ escape_sequence | (!"\'" ~ ANY) } 55 | 56 | alpha = _{ 'a'..'z' | 'A'..'Z' } 57 | alphanumeric = _{ alpha | '0'..'9' } 58 | 59 | negative_decimal = _{ "-" ~ nonnegative_decimal } 60 | digit = _{ "0" | nonzero } 61 | nonzero = _{ '1'..'9' } 62 | 63 | escape_sequence = _{ "\\\\" | "\\\"" | "\\\'" | "\\n" | "\\r" | "\\t" | "\\0" } 64 | 65 | whitespace_char = _{ " " | "\t" | "\u{000C}" | "\r" | "\n" } 66 | WHITESPACE = _{ (whitespace_char)+ } 67 | -------------------------------------------------------------------------------- /interact_prompt/src/registry.rs: -------------------------------------------------------------------------------- 1 | //! Interact Prompt registry for accessible state. 2 | 3 | use std::cell::RefCell; 4 | use std::sync::Mutex; 5 | 6 | use interact::{Access, Root, RootLocal, RootSend}; 7 | 8 | /// The `Send` Registry manages state roots of the whole process. 9 | pub struct SendRegistry { 10 | root: Mutex, 11 | } 12 | 13 | lazy_static! { 14 | static ref REGISTRY: SendRegistry = { 15 | SendRegistry { 16 | root: Mutex::new(RootSend::new()), 17 | } 18 | }; 19 | } 20 | 21 | impl SendRegistry { 22 | /// Insert new states into the root. 23 | pub fn insert(string: &'static str, item: Box) { 24 | let mut root = REGISTRY.root.lock().unwrap(); 25 | 26 | root.owned.insert(string, item); 27 | } 28 | 29 | #[doc(hidden)] 30 | pub(crate) fn with_root(f: F) -> R 31 | where 32 | F: FnOnce(&mut RootSend) -> R, 33 | { 34 | let mut root = REGISTRY.root.lock().unwrap(); 35 | f(&mut *root) 36 | } 37 | } 38 | 39 | /// The Local Registry manages state roots of per-thread states. 40 | pub struct LocalRegistry { 41 | root: RootLocal, 42 | } 43 | 44 | thread_local! { 45 | #[doc(hidden)] 46 | pub static LOCAL_REGISTRY: RefCell = { 47 | RefCell::new(LocalRegistry { 48 | root: RootLocal::new(), 49 | }) 50 | }; 51 | } 52 | 53 | impl LocalRegistry { 54 | /// Insert new states into the root. 55 | pub fn insert(string: &'static str, item: Box) { 56 | LOCAL_REGISTRY.with(|reg| { 57 | reg.borrow_mut().root.owned.insert(string, item); 58 | }); 59 | } 60 | } 61 | 62 | #[doc(hidden)] 63 | pub(crate) fn with_root(f: F) -> R 64 | where 65 | F: FnOnce(&mut Root) -> R, 66 | { 67 | LOCAL_REGISTRY.with(|local_reg| { 68 | SendRegistry::with_root(|send_reg| { 69 | let mut local_reg = local_reg.borrow_mut(); 70 | let mut root = Root { 71 | send: Some(send_reg), 72 | local: Some(&mut local_reg.root), 73 | }; 74 | f(&mut root) 75 | }) 76 | }) 77 | } 78 | -------------------------------------------------------------------------------- /interact/src/access/derefs.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | use std::ops::DerefMut; 3 | use std::rc::Rc; 4 | use std::sync::Arc; 5 | 6 | use crate::access::{Access, AssignError, ImmutAccess, MutAccess, ReflectMut}; 7 | use crate::deser::{self, Deser}; 8 | 9 | impl<'a, T: 'a> Access for &'a T 10 | where 11 | T: Access, 12 | { 13 | fn immut_access(&self) -> ImmutAccess { 14 | (*self).immut_access() 15 | } 16 | 17 | fn mut_access(&mut self) -> MutAccess { 18 | MutAccess::no_funcs(ReflectMut::Immutable) 19 | } 20 | 21 | fn mut_assign<'c, 'b>( 22 | &mut self, 23 | _tracker: &mut deser::Tracker<'c, 'b>, 24 | _probe_only: bool, 25 | ) -> Result<(), AssignError> { 26 | Err(AssignError::Immutable) 27 | } 28 | } 29 | 30 | impl<'a, T: 'a> Access for &'a mut T 31 | where 32 | T: Access + Deser, 33 | { 34 | fn immut_access(&self) -> ImmutAccess { 35 | (**self).immut_access() 36 | } 37 | 38 | fn mut_access(&mut self) -> MutAccess { 39 | (*self).mut_access() 40 | } 41 | 42 | mut_assign_deser!(); 43 | } 44 | 45 | impl Access for Box 46 | where 47 | T: Access + Deser, 48 | { 49 | fn immut_access(&self) -> ImmutAccess { 50 | self.deref().immut_access() 51 | } 52 | 53 | fn mut_access(&mut self) -> MutAccess { 54 | self.deref_mut().mut_access() 55 | } 56 | 57 | mut_assign_deser!(); 58 | } 59 | 60 | impl Access for Rc 61 | where 62 | T: Access + Deser, 63 | { 64 | fn immut_access(&self) -> ImmutAccess { 65 | self.deref().immut_access() 66 | } 67 | 68 | fn mut_access(&mut self) -> MutAccess { 69 | match Rc::get_mut(self) { 70 | None => MutAccess::no_funcs(ReflectMut::Immutable), 71 | Some(mutref) => mutref.mut_access(), 72 | } 73 | } 74 | 75 | mut_assign_deser!(); 76 | } 77 | 78 | impl Access for Arc 79 | where 80 | T: Access + Deser, 81 | { 82 | fn immut_access(&self) -> ImmutAccess { 83 | self.deref().immut_access() 84 | } 85 | 86 | fn mut_access(&mut self) -> MutAccess { 87 | match Arc::get_mut(self) { 88 | None => MutAccess::no_funcs(ReflectMut::Immutable), 89 | Some(mutref) => mutref.mut_access(), 90 | } 91 | } 92 | 93 | mut_assign_deser!(); 94 | } 95 | -------------------------------------------------------------------------------- /interact/tests/common/pseudo_mutex.rs: -------------------------------------------------------------------------------- 1 | /// Cover private Mutex derive 2 | /// 3 | /// This module checks that we can manually derive `Access` and `Deser` for types for which we 4 | /// cannot use #[derive(Interact)] 5 | /// 6 | use interact::access::{Access, ReflectDirect}; 7 | use interact::climber::{ClimbError, Climber}; 8 | use interact::deser::{self, Tracker}; 9 | use interact::{Deser, NodeTree, Reflector}; 10 | 11 | use std::ops::Deref; 12 | use std::ops::DerefMut; 13 | use std::sync::Arc; 14 | 15 | pub struct PseudoMutex { 16 | _t: T, 17 | } 18 | 19 | impl PseudoMutex { 20 | pub fn new(_t: T) -> Self { 21 | PseudoMutex { _t } 22 | } 23 | } 24 | 25 | struct Guard<'a, T>(&'a T); 26 | 27 | impl PseudoMutex { 28 | fn lock(&self) -> Guard { 29 | Guard(&self._t) 30 | } 31 | } 32 | 33 | impl<'a, T> Deref for Guard<'a, T> { 34 | type Target = T; 35 | 36 | fn deref(&self) -> &T { 37 | self.0 38 | } 39 | } 40 | 41 | impl<'a, T> DerefMut for Guard<'a, T> { 42 | fn deref_mut(&mut self) -> &mut T { 43 | panic!("cannot get mutable refs for PseudoMutex"); 44 | } 45 | } 46 | 47 | impl ReflectDirect for PseudoMutex 48 | where 49 | T: Access, 50 | { 51 | fn immut_reflector(&self, reflector: &Arc) -> NodeTree { 52 | let locked = self.lock(); 53 | Reflector::reflect(reflector, &*locked) 54 | } 55 | 56 | fn immut_climber<'a>(&self, climber: &mut Climber<'a>) -> Result, ClimbError> { 57 | let save = climber.clone(); 58 | let retval = { 59 | let locked = self.lock(); 60 | climber.general_access_immut(&*locked).map(Some) 61 | }; 62 | 63 | if let Err(ClimbError::NeedMutPath) = &retval { 64 | *climber = save; 65 | let mut locked = self.lock(); 66 | climber.general_access_mut(&mut *locked).map(Some) 67 | } else { 68 | retval 69 | } 70 | } 71 | 72 | fn mut_climber<'a>( 73 | &mut self, 74 | climber: &mut Climber<'a>, 75 | ) -> Result, ClimbError> { 76 | let mut locked = self.lock(); 77 | climber.general_access_mut(&mut *locked).map(Some) 78 | } 79 | } 80 | 81 | impl Deser for PseudoMutex 82 | where 83 | T: Deser, 84 | { 85 | fn deser<'a, 'b>(tracker: &mut Tracker<'a, 'b>) -> deser::Result { 86 | Ok(PseudoMutex::new(T::deser(tracker)?)) 87 | } 88 | } 89 | 90 | use interact::derive_interact_extern_opqaue; 91 | derive_interact_extern_opqaue! { 92 | struct PseudoMutex; 93 | } 94 | -------------------------------------------------------------------------------- /interact/src/util/assist.rs: -------------------------------------------------------------------------------- 1 | /// Given a list of items, this provides assistance for completing them. 2 | #[derive(Debug, Eq, PartialEq, Clone, Default)] 3 | pub struct Assist { 4 | /// How many of the first items are valid 5 | valid: usize, 6 | 7 | /// Following the valid items, how many more items will be valid once 8 | /// more items are added 9 | pending: usize, 10 | 11 | /// Among these pending items, how many are have special marking, 12 | /// from the end of the pending list. 13 | pending_special: usize, 14 | 15 | /// An optional list of items that can follow the valid+pending ones. 16 | next_options: NextOptions, 17 | } 18 | 19 | #[derive(Debug, Eq, PartialEq, Clone)] 20 | pub enum NextOptions { 21 | NoOptions, 22 | Avail(usize, Vec), 23 | } 24 | 25 | impl Default for NextOptions { 26 | fn default() -> Self { 27 | NextOptions::NoOptions 28 | } 29 | } 30 | 31 | impl NextOptions { 32 | pub fn into_position(self, valid: usize) -> (usize, Vec) { 33 | match self { 34 | NextOptions::NoOptions => (0, vec![]), 35 | NextOptions::Avail(pos, v) => (valid + pos, v), 36 | } 37 | } 38 | } 39 | 40 | impl Assist { 41 | pub fn pend(&mut self, count: usize) { 42 | self.pending += count; 43 | } 44 | 45 | pub fn pend_one(&mut self) { 46 | self.pend(1); 47 | } 48 | 49 | pub fn pending(&self) -> usize { 50 | self.pending 51 | } 52 | 53 | pub fn has_pending(&self) -> bool { 54 | self.pending > 0 55 | } 56 | 57 | pub fn commit_pending(&mut self) { 58 | self.valid += self.pending; 59 | self.pending = 0; 60 | self.pending_special = 0; 61 | } 62 | 63 | pub fn next_options(mut self, next_options: NextOptions) -> Self { 64 | self.next_options = next_options; 65 | self 66 | } 67 | 68 | pub fn set_next_options(&mut self, next_options: NextOptions) { 69 | self.next_options = next_options; 70 | } 71 | 72 | pub fn set_pending_special(&mut self, pending_special: usize) { 73 | self.pending_special = pending_special 74 | } 75 | 76 | pub fn with_valid(mut self, valid: usize) -> Self { 77 | self.valid += valid; 78 | self 79 | } 80 | 81 | pub fn dismantle(self) -> (usize, usize, usize, NextOptions) { 82 | let Self { 83 | valid, 84 | pending, 85 | pending_special, 86 | next_options, 87 | } = self; 88 | 89 | (valid, pending, pending_special, next_options) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /interact/src/deser.rs: -------------------------------------------------------------------------------- 1 | use crate::{ExpectTree, Token, TokenKind, TokenVec}; 2 | 3 | #[derive(Debug, Eq, PartialEq)] 4 | pub enum DeserError { 5 | EndOfTokenList, 6 | NumberTooLarge, 7 | NumberTooSmall, 8 | UnexpectedToken, 9 | Unbuildable, 10 | } 11 | 12 | pub struct Tracker<'a, 'b> { 13 | expect: &'b mut ExpectTree>, 14 | tokenvec: &'b mut TokenVec<'a>, 15 | steps: usize, 16 | } 17 | 18 | pub type Result = std::result::Result; 19 | 20 | impl<'a, 'b> Tracker<'a, 'b> { 21 | pub fn new(expect: &'b mut ExpectTree>, tokenvec: &'b mut TokenVec<'a>) -> Self { 22 | Self { 23 | expect, 24 | tokenvec, 25 | steps: 0, 26 | } 27 | } 28 | 29 | pub fn possible_token(&mut self, token: Token<'static>) { 30 | self.expect.advance(token); 31 | self.expect.retract_one(); 32 | } 33 | 34 | pub fn try_token(&mut self, token: &Token<'static>) -> Result { 35 | if !self.tokenvec.has_remaining() { 36 | let mut token = token.clone(); 37 | if let Some(last) = self.expect.last() { 38 | if let TokenKind::Comma = &last.kind { 39 | if last.space_suffix() == 0 { 40 | token.space_diff += 1; 41 | } 42 | } 43 | } else if self.steps == 0 { 44 | token.space_diff += 1; 45 | } 46 | self.expect.advance(token); 47 | Ok(false) 48 | } else if self.tokenvec.top().similar(token) { 49 | self.step(); 50 | Ok(true) 51 | } else if self.tokenvec.top().is_prefix_of(token) { 52 | self.expect.advance(token.clone()); 53 | Ok(false) 54 | } else { 55 | Err(DeserError::UnexpectedToken) 56 | } 57 | } 58 | 59 | pub fn has_remaining(&self) -> bool { 60 | self.tokenvec.has_remaining() 61 | } 62 | 63 | pub fn top(&self) -> &Token<'a> { 64 | self.tokenvec.top() 65 | } 66 | 67 | pub fn top_kind(&self) -> &TokenKind { 68 | &self.top().kind 69 | } 70 | 71 | pub fn step(&mut self) { 72 | *self.expect = ExpectTree::new(); 73 | self.tokenvec.step(); 74 | self.steps += 1; 75 | } 76 | } 77 | 78 | pub trait Deser: Sized { 79 | fn deser<'a, 'b>(_tracker: &mut Tracker<'a, 'b>) -> Result { 80 | return Err(DeserError::Unbuildable); 81 | } 82 | } 83 | 84 | mod basic; 85 | mod btreemap; 86 | mod derefs; 87 | mod hashmap; 88 | mod hashset; 89 | mod instant; 90 | mod mutex; 91 | mod refcell; 92 | mod tuple; 93 | mod vec; 94 | -------------------------------------------------------------------------------- /interact/src/deser/tuple.rs: -------------------------------------------------------------------------------- 1 | use crate::deser::{Deser, Result, Tracker}; 2 | use crate::tokens::{Token, TokenKind}; 3 | 4 | macro_rules! tuple { 5 | ({ $(($n:ident, $i:tt)),* }) => { 6 | impl<$($n),*> Deser for ($($n),*) 7 | where $($n : Deser),* 8 | { 9 | fn deser<'a, 'b>(tracker: &mut Tracker<'a, 'b>) -> Result { 10 | let open = Token::new_borrowed(TokenKind::TupleOpen, "("); 11 | let close = Token::new_borrowed(TokenKind::TupleClose, ")"); 12 | let comma = Token::new_borrowed(TokenKind::Comma, ", "); 13 | 14 | tracker.try_token(&open)?; 15 | Ok(($( 16 | { 17 | let x : $n = Deser::deser(tracker)?; 18 | if $i { 19 | tracker.try_token(&close)?; 20 | } else { 21 | tracker.try_token(&comma)?; 22 | } 23 | x 24 | } 25 | ),*)) 26 | } 27 | } 28 | }; 29 | } 30 | 31 | tuple!({(A, false), (B, true)}); 32 | tuple!({(A, false), (B, false), (C, true)}); 33 | tuple!({(A, false), (B, false), (C, false), (D, true)}); 34 | tuple!({(A, false), (B, false), (C, false), (D, false), (E, true)}); 35 | tuple!({(A, false), (B, false), (C, false), (D, false), (E, false), (F, true)}); 36 | tuple!({(A, false), (B, false), (C, false), (D, false), (E, false), (F, false), (G, true)}); 37 | tuple!({(A, false), (B, false), (C, false), (D, false), (E, false), (F, false), (G, false), (H, true)}); 38 | tuple!({(A, false), (B, false), (C, false), (D, false), (E, false), (F, false), (G, false), (H, false), (I, true)}); 39 | tuple!({(A, false), (B, false), (C, false), (D, false), (E, false), (F, false), (G, false), (H, false), (I, false), (J, true)}); 40 | 41 | impl Deser for (A,) 42 | where 43 | A: Deser, 44 | { 45 | fn deser<'a, 'b>(tracker: &mut Tracker<'a, 'b>) -> Result { 46 | let open = Token::new_borrowed(TokenKind::TupleOpen, "("); 47 | let close = Token::new_borrowed(TokenKind::TupleClose, ")"); 48 | tracker.try_token(&open)?; 49 | let a = Deser::deser(tracker)?; 50 | // TODO: allow for an extra ',' token 51 | tracker.try_token(&close)?; 52 | Ok((a,)) 53 | } 54 | } 55 | 56 | impl Deser for () { 57 | fn deser<'a, 'b>(tracker: &mut Tracker<'a, 'b>) -> Result { 58 | let open = Token::new_borrowed(TokenKind::TupleOpen, "("); 59 | let close = Token::new_borrowed(TokenKind::TupleClose, ")"); 60 | tracker.try_token(&open)?; 61 | tracker.try_token(&close)?; 62 | Ok(()) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /interact/src/access/btreemap.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | use std::sync::Arc; 3 | 4 | use crate::access::{iter::ReflectIter, Access, ReflectDirect}; 5 | use crate::climber::{ClimbError, Climber}; 6 | use crate::deser; 7 | use crate::node_tree::NodeTree; 8 | use crate::reflector::Reflector; 9 | 10 | impl<'a, K, V> ReflectIter<(&'a dyn Access, &'a dyn Access)> 11 | for std::collections::btree_map::Iter<'a, K, V> 12 | where 13 | K: Eq + Access, 14 | V: Access, 15 | { 16 | fn reflect_next(&mut self) -> Option<(&'a dyn Access, &'a dyn Access)> { 17 | match self.next() { 18 | None => None, 19 | Some((key, value)) => Some((key, value)), 20 | } 21 | } 22 | } 23 | 24 | impl ReflectDirect for BTreeMap 25 | where 26 | K: Eq + Ord + Access + deser::Deser, 27 | V: Access, 28 | { 29 | fn immut_reflector(&self, reflector: &Arc) -> NodeTree { 30 | let mut i = Box::new(self.iter()); 31 | Reflector::reflect_map(reflector, &mut *i, "BTreeMap") 32 | } 33 | 34 | fn immut_climber<'a>(&self, climber: &mut Climber<'a>) -> Result, ClimbError> { 35 | if !climber.open_bracket() { 36 | return Ok(None); 37 | } 38 | 39 | let v = K::deser(&mut climber.borrow_tracker()) 40 | .map(|x| >::get(self, &x)) 41 | .map(|x| x.map(|y| y as &dyn Access)); 42 | let v = match v { 43 | Ok(None) => return Err(ClimbError::NotFound), 44 | Err(err) => return Err(ClimbError::DeserError(err)), 45 | Ok(Some(v)) => v, 46 | }; 47 | 48 | climber.close_bracket()?; 49 | 50 | return climber.general_access_immut(v).map(Some); 51 | } 52 | 53 | fn mut_climber<'a>( 54 | &mut self, 55 | climber: &mut Climber<'a>, 56 | ) -> Result, ClimbError> { 57 | if !climber.open_bracket() { 58 | return Ok(None); 59 | } 60 | 61 | let v = match K::deser(&mut climber.borrow_tracker()) { 62 | Ok(x) => Ok(match >::get_mut(self, &x) { 63 | None => None, 64 | Some(x) => Some(x), 65 | }), 66 | Err(e) => Err(e), 67 | }; 68 | let v = match v { 69 | Ok(None) => return Err(ClimbError::NotFound), 70 | Err(err) => return Err(ClimbError::DeserError(err)), 71 | Ok(Some(v)) => v, 72 | }; 73 | 74 | climber.close_bracket()?; 75 | 76 | return climber.general_access_mut(v).map(Some); 77 | } 78 | } 79 | 80 | use interact_derive::derive_interact_opaque; 81 | 82 | derive_interact_opaque! { 83 | #[interact(immut_fn(len()))] 84 | struct BTreeMap 85 | where 86 | K: Eq + Ord + deser::Deser; 87 | } 88 | -------------------------------------------------------------------------------- /interact/src/access/hashmap.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::hash::{BuildHasher, Hash}; 3 | use std::sync::Arc; 4 | 5 | use crate::access::{iter::ReflectIter, Access, ReflectDirect}; 6 | use crate::climber::{ClimbError, Climber}; 7 | use crate::deser; 8 | use crate::node_tree::NodeTree; 9 | use crate::reflector::Reflector; 10 | 11 | impl<'a, K, V> ReflectIter<(&'a dyn Access, &'a dyn Access)> 12 | for std::collections::hash_map::Iter<'a, K, V> 13 | where 14 | K: Eq + Hash + Access, 15 | V: Access, 16 | { 17 | fn reflect_next(&mut self) -> Option<(&'a dyn Access, &'a dyn Access)> { 18 | match self.next() { 19 | None => None, 20 | Some((key, value)) => Some((key, value)), 21 | } 22 | } 23 | } 24 | 25 | impl ReflectDirect for HashMap 26 | where 27 | K: Eq + Hash + Access + deser::Deser, 28 | V: Access, 29 | S: BuildHasher, 30 | { 31 | fn immut_reflector(&self, reflector: &Arc) -> NodeTree { 32 | let mut i = Box::new(self.iter()); 33 | Reflector::reflect_map(reflector, &mut *i, "HashMap") 34 | } 35 | 36 | fn immut_climber<'a>(&self, climber: &mut Climber<'a>) -> Result, ClimbError> { 37 | if !climber.open_bracket() { 38 | return Ok(None); 39 | } 40 | 41 | let v = K::deser(&mut climber.borrow_tracker()) 42 | .map(|x| >::get(self, &x)) 43 | .map(|x| x.map(|y| y as &dyn Access)); 44 | let v = match v { 45 | Ok(None) => return Err(ClimbError::NotFound), 46 | Err(err) => return Err(ClimbError::DeserError(err)), 47 | Ok(Some(v)) => v, 48 | }; 49 | 50 | climber.close_bracket()?; 51 | 52 | return climber.general_access_immut(v).map(Some); 53 | } 54 | 55 | fn mut_climber<'a>( 56 | &mut self, 57 | climber: &mut Climber<'a>, 58 | ) -> Result, ClimbError> { 59 | if !climber.open_bracket() { 60 | return Ok(None); 61 | } 62 | 63 | let v = match K::deser(&mut climber.borrow_tracker()) { 64 | Ok(x) => Ok(match >::get_mut(self, &x) { 65 | None => None, 66 | Some(x) => Some(x), 67 | }), 68 | Err(e) => Err(e), 69 | }; 70 | let v = match v { 71 | Ok(None) => return Err(ClimbError::NotFound), 72 | Err(err) => return Err(ClimbError::DeserError(err)), 73 | Ok(Some(v)) => v, 74 | }; 75 | 76 | climber.close_bracket()?; 77 | 78 | return climber.general_access_mut(v).map(Some); 79 | } 80 | } 81 | 82 | use interact_derive::derive_interact_opaque; 83 | 84 | derive_interact_opaque! { 85 | #[interact(skip_bound(S))] 86 | #[interact(immut_fn(len()))] 87 | struct HashMap 88 | where 89 | K: Eq + std::hash::Hash + deser::Deser, 90 | S: std::hash::BuildHasher; 91 | } 92 | -------------------------------------------------------------------------------- /interact/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Interact 2 | //! 3 | //! Interact is a framework for friendly online introspection of the running program state in an 4 | //! intuitive command-line interactive way. 5 | //! 6 | //! While dynamically-typed interpreted languages offer the advantage of allowing to look at a 7 | //! running program state using a prompt, compiled languages often do not provide that feature. 8 | //! Being hard as it is to introduce interpreters into compiled languages, the Interact project 9 | //! aimes to provide a midway solution using stable Rust. 10 | //! 11 | //! # Usage 12 | //! 13 | //! NOTE: **Unless you are manually extending types for use under Interact, you probably don't need 14 | //! most of the items that are exported in this crate**. Instead, look for the `interact_prompt` crate. 15 | //! 16 | //! # Design 17 | //! 18 | //! Interact introduces a series of traits, the main ones are `Access` and `Deser` trait. Those 19 | //! crates can be custom-derived using `#[derive(Interact)]`, or be derived manually. 20 | //! 21 | //! The `Access` provides two methods that return special accessor trait object types. Please 22 | //! read the documentation for the `access` part of Interact. 23 | //! 24 | //! The `Deser` trait is a special deserializer that allows for online interactive hints at 25 | //! non-ambiguous parse points. 26 | //! 27 | //! Further relevent bits that comprise Interact are: 28 | //! 29 | //! * `reflector`, when provided at type it will generate a representation of it, while handling 30 | //! reference cycles, imposed output limitations, mutexs, and customized in-process indirections. 31 | //! * `climber`, which when given a Rust-like expression of an inner value, knows how to go from an 32 | //! Interact root down to a field. 33 | 34 | #[macro_use] 35 | extern crate pest_derive; 36 | 37 | // 38 | // All the `pub use` here shows exactly what are the names that this crate exports. 39 | // 40 | #[doc(hidden)] 41 | macro_rules! try_seen_dyn { 42 | ($e:expr, $self:expr) => { 43 | { 44 | let ptr = (&$e as *const _) as usize; 45 | let obj_ptr = (unsafe { *(ptr as *const usize) }, unsafe { 46 | *(ptr as *const usize).offset(1) 47 | }); 48 | 49 | match Reflector::seen_ptr($self, obj_ptr) { 50 | Ok(v) => return v, 51 | Err(meta) => meta, 52 | } 53 | } 54 | } 55 | } 56 | 57 | // tokens 58 | mod tokens; 59 | pub use crate::tokens::{Token, TokenKind, TokenVec}; 60 | 61 | // deser 62 | pub mod deser; 63 | #[doc(inline)] 64 | pub use crate::deser::Deser; 65 | 66 | // reflector 67 | #[macro_use] 68 | mod reflector; 69 | pub use crate::reflector::Reflector; 70 | 71 | // access 72 | pub mod access; 73 | #[doc(hidden)] 74 | pub use crate::access::{ 75 | derive::{Enum, ReflectEnum, ReflectStruct, Struct, StructKind}, 76 | iter::ReflectIter, 77 | Function, 78 | }; 79 | 80 | #[doc(inline)] 81 | pub use crate::access::{ 82 | deser_assign, Access, AssignError, CallError, ImmutAccess, MutAccess, Reflect, ReflectDirect, 83 | ReflectIndirect, ReflectMut, RetValCallback, 84 | }; 85 | 86 | // #derive 87 | #[doc(hidden)] 88 | pub use interact_derive::derive_interact_extern_opqaue; 89 | 90 | pub use interact_derive::Interact; 91 | 92 | // util 93 | mod util; 94 | pub use crate::assist::{Assist, NextOptions}; 95 | pub use crate::node_tree::{NodeInfo, NodeTree}; 96 | 97 | // climber 98 | pub mod climber; 99 | #[doc(inline)] 100 | pub use crate::climber::{ClimbError, Climber}; 101 | 102 | #[doc(hidden)] 103 | pub use crate::climber::{EnumOrStruct, EnumOrStructMut}; 104 | 105 | // root 106 | pub mod root; 107 | #[doc(inline)] 108 | pub use crate::root::{Root, RootLocal, RootSend}; 109 | 110 | // 111 | // Internally re-exported 112 | // 113 | use crate::expect::ExpectTree; 114 | use crate::util::{assist, expect, node_tree}; 115 | -------------------------------------------------------------------------------- /doc/src/examples/actix.md: -------------------------------------------------------------------------------- 1 | # Actix 2 | 3 | To get a taste of Interact as applied to actual servers, you can try the Interact-enabled Actix chat demo (originally from [here](https://github.com/actix/actix/tree/master/examples/chat)). 4 | 5 | While the state of an Actix program is spread across a stack of `Future`s that may exist in multiple process thread, Interact has no difficulty in traversing it and presenting a whole picture of the server. 6 | 7 | ### Summary of changes 8 | 9 | To enable this example, there were two changes: 10 | 11 | * Changes in Actix core ([Github link](https://github.com/interact-rs/actix/compare/interact-rs:base...interact-rs:interact-addr)), that enable Interact for the `Addr` Actor messaging proxy. 12 | * Changes to Actix chat app ([Github link](https://github.com/interact-rs/actix/compare/interact-rs:interact-addr...interact-rs:interact-chat)), which add `#[derive(Interact)]` for its types, and invocation of the Interact prompt. 13 | 14 | 15 | ## Demo 16 | 17 | ```shell 18 | git clone https://github.com/interact-rs/actix 19 | cd actix/examples/chat 20 | cargo run --bin server 21 | ``` 22 | 23 | Executing the server presents a prompt in a dedicated Interact thread, while the server functionality runs in the process's background: 24 | 25 | ```shell 26 | Running chat server on 127.0.0.1:12345 27 | Rust `interact`, type '?' for more information 28 | >>> 29 | ``` 30 | 31 | You can examine the server state: 32 | 33 | ```rust,ignore 34 | >>> server 35 | ChatServer { sessions: HashMap {}, rooms: HashMap { "Main": HashSet {} } } 36 | ``` 37 | 38 | In parallel, run two clients using `cargo run --bin client`, and re-examine the server's state: 39 | 40 | ```rust,ignore 41 | >>> server 42 | [#1] ChatServer { 43 | sessions: HashMap { 44 | 8426954607288880898: ChatSession { id: 8426954607288880898, 45 | addr: [#1], hb: 374.307146ms, room: "Main" }, 46 | 9536033526192464616: ChatSession { id: 9536033526192464616, 47 | addr: [#1], hb: 513.580812ms, room: "Main" } 48 | }, 49 | rooms: HashMap { "Main": HashSet { 8426954607288880898, 9536033526192464616 } } 50 | } 51 | ``` 52 | 53 | The reason for `#[1]` is the loop that is detected by traversal of ChatSession's `addr`, which loops back into `ChatServer`. 54 | 55 | You can use Interact to print only field of rooms: 56 | ```rust,ignore 57 | >>> server.rooms 58 | HashMap { "Main": HashSet { 8426954607288880898, 9536033526192464616 } } 59 | ``` 60 | 61 | Or access one of the sessions: 62 | 63 | ```rust,ignore 64 | >>> server.sessions[8426954607288880898] 65 | [#1] ChatSession { 66 | id: 8426954607288880898, 67 | addr: [#2] ChatServer { 68 | sessions: HashMap { 69 | 8426954607288880898: [#1], 70 | 9536033526192464616: ChatSession { id: 9536033526192464616, 71 | addr: [#2], hb: 759.986694ms, room: "Main" } 72 | }, 73 | rooms: HashMap { "Main": HashSet { 8426954607288880898, 9536033526192464616 } } 74 | }, 75 | hb: 632.849822ms, 76 | room: "Main" 77 | } 78 | ``` 79 | 80 | See the 'hb' field get updated: 81 | 82 | ```rust,ignore 83 | >>> server.sessions[8426954607288880898].hb 84 | 716.16972ms 85 | >>> server.sessions[8426954607288880898].hb 86 | 10.398845ms 87 | ``` 88 | 89 | Modify the room's name: 90 | 91 | ```rust,ignore 92 | >>> server.sessions[8426954607288880898].room = "Boo" 93 | ``` 94 | 95 | See that it was indeed modified: 96 | 97 | ```rust,ignore 98 | >>> server.sessions[8426954607288880898] 99 | [#1] ChatSession { 100 | id: 8426954607288880898, 101 | addr: [#2] ChatServer { 102 | sessions: HashMap { 103 | 8426954607288880898: [#1], 104 | 9536033526192464616: ChatSession { id: 9536033526192464616, 105 | addr: [#2], hb: 219.435076ms, room: "Main" } 106 | }, 107 | rooms: HashMap { "Main": HashSet { 8426954607288880898, 9536033526192464616 } } 108 | }, 109 | hb: 112.667608ms, 110 | room: "Boo" 111 | } 112 | 113 | ``` 114 | -------------------------------------------------------------------------------- /interact/src/util/expect.rs: -------------------------------------------------------------------------------- 1 | //! The `Expect` struct manages the tree of expectations during parsing. 2 | //! 3 | //! While parsing an expression for `Token`s, the parser may have expectations at each point in the 4 | //! way of what tokens should appear. In case these tokens do not show up, we can remember what 5 | //! tokens could have appeared and then backtrack the parsing. This crates a tree that can be later 6 | //! flattened in order to present a list of options. 7 | //! 8 | //! This allows to implement auto-completion and hints so that users can type the minimal amount of 9 | //! characters. That is especially relevant in the case where there's only one token that can 10 | //! follow. 11 | 12 | #[derive(Debug, Eq, PartialEq, Clone)] 13 | struct Expect { 14 | item: T, 15 | next: Vec>, 16 | } 17 | 18 | impl Expect 19 | where 20 | T: Eq + PartialEq + Clone, 21 | { 22 | fn into_flatten(self) -> Vec> { 23 | if self.next.is_empty() { 24 | return vec![vec![self.item]]; 25 | } 26 | 27 | let mut vs = vec![]; 28 | for n in self.next { 29 | for mut v in n.into_flatten() { 30 | v.push(self.item.clone()); 31 | vs.push(v); 32 | } 33 | } 34 | vs 35 | } 36 | } 37 | 38 | #[derive(Debug, Eq, PartialEq, Clone)] 39 | pub struct ExpectTree { 40 | path: Vec, 41 | tree: Vec>, 42 | } 43 | 44 | impl ExpectTree 45 | where 46 | T: Eq + PartialEq + Clone, 47 | { 48 | /// Construct an empty tree 49 | pub fn new() -> Self { 50 | Self { 51 | path: vec![], 52 | tree: vec![], 53 | } 54 | } 55 | 56 | /// Register `value` as expected at this stage, and advance to the next token. 57 | pub fn advance(&mut self, value: T) { 58 | let mut r = &mut self.tree; 59 | 60 | for idx in &self.path { 61 | r = &mut r[*idx].next; 62 | } 63 | 64 | let mut idx = 0; 65 | for s in r.iter() { 66 | if s.item == value { 67 | self.path.push(idx); 68 | return; 69 | } 70 | idx += 1; 71 | } 72 | 73 | r.push(Expect { 74 | item: value, 75 | next: vec![], 76 | }); 77 | self.path.push(idx); 78 | } 79 | 80 | #[cfg(test)] 81 | fn path_len(&self) -> usize { 82 | self.path.len() 83 | } 84 | 85 | /// Back track one token. 86 | pub fn retract_one(&mut self) { 87 | self.path.truncate(self.path.len() - 1); 88 | } 89 | 90 | /// Back track to a certain length. 91 | pub fn retract_path(&mut self, old_len: usize) { 92 | assert!(old_len <= self.path.len()); 93 | self.path.truncate(old_len); 94 | } 95 | 96 | /// Resolve the tree into a list of possible token vectors, based on 97 | /// where that were junctions in the expectation tree. 98 | pub fn into_flatten(self) -> Vec> { 99 | let mut tv = vec![]; 100 | 101 | for v in self.tree { 102 | for mut t in v.into_flatten() { 103 | t.reverse(); 104 | tv.push(t) 105 | } 106 | } 107 | 108 | tv 109 | } 110 | 111 | /// Return a reference to the last added token. 112 | pub fn last(&self) -> Option<&T> { 113 | let mut r = &self.tree; 114 | let mut item = None; 115 | 116 | for idx in &self.path { 117 | item = Some(&r[*idx].item); 118 | r = &r[*idx].next; 119 | } 120 | 121 | item 122 | } 123 | } 124 | 125 | #[cfg(test)] 126 | mod tests { 127 | #[test] 128 | fn main() { 129 | use super::*; 130 | 131 | let mut et = ExpectTree::new(); 132 | assert_eq!(et.last(), None); 133 | 134 | et.advance(2); 135 | assert_eq!(et.clone().into_flatten(), vec![vec![2]]); 136 | assert_eq!(et.last(), Some(&2)); 137 | 138 | et.advance(3); 139 | assert_eq!(et.clone().into_flatten(), vec![vec![2, 3]]); 140 | assert_eq!(et.last(), Some(&3)); 141 | 142 | et.retract_path(et.path_len() - 1); 143 | assert_eq!(et.last(), Some(&2)); 144 | 145 | et.advance(3); 146 | assert_eq!(et.clone().into_flatten(), vec![vec![2, 3]]); 147 | 148 | et.retract_path(et.path_len() - 1); 149 | et.advance(4); 150 | assert_eq!(et.clone().into_flatten(), vec![vec![2, 3], vec![2, 4]]); 151 | 152 | et.retract_path(et.path_len() - 2); 153 | et.advance(1); 154 | assert_eq!( 155 | et.clone().into_flatten(), 156 | vec![vec![2, 3], vec![2, 4], vec![1]] 157 | ); 158 | 159 | et.retract_path(et.path_len() - 1); 160 | et.advance(2); 161 | et.advance(3); 162 | et.advance(6); 163 | assert_eq!( 164 | et.clone().into_flatten(), 165 | vec![vec![2, 3, 6], vec![2, 4], vec![1]] 166 | ); 167 | 168 | et.retract_path(et.path_len() - 1); 169 | et.advance(7); 170 | assert_eq!( 171 | et.clone().into_flatten(), 172 | vec![vec![2, 3, 6], vec![2, 3, 7], vec![2, 4], vec![1]] 173 | ); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /interact/tests/integ.rs: -------------------------------------------------------------------------------- 1 | extern crate interact; 2 | 3 | use pretty_assertions::assert_eq; 4 | mod common; 5 | use common::{Basic, Complex, LocalRcLoop, Rand}; 6 | 7 | struct Context { 8 | count: usize, 9 | check: bool, 10 | } 11 | 12 | macro_rules! verify { 13 | ($self:expr, $e:expr => $result:tt) => { 14 | let e = $e; 15 | let str_e = format!("{:?}", e); 16 | 17 | if $self.check { 18 | if str_e != $result { 19 | println!(""); 20 | println!("Failed:"); 21 | println!(""); 22 | println!("verify!(self, {} => {:?});", stringify!($e), $result); 23 | println!(""); 24 | 25 | let _ = std::panic::catch_unwind(|| { 26 | assert_eq!(str_e, $result); 27 | }); 28 | 29 | println!("Correct this by having:"); 30 | println!(""); 31 | println!("verify!(self, {} => {:?});", stringify!($e), str_e); 32 | println!(""); 33 | 34 | $self.count += 1; 35 | } 36 | } else { 37 | println!("verify!(self, {} => {:?});", stringify!($e), str_e); 38 | } 39 | }; 40 | } 41 | 42 | #[test] 43 | fn main() { 44 | let mut context = Context { 45 | count: 0, 46 | check: true, 47 | }; 48 | context.main(); 49 | 50 | if context.count > 0 { 51 | { 52 | println!(); 53 | println!("Expected test manifest:"); 54 | println!(); 55 | let mut context = Context { 56 | count: 0, 57 | check: false, 58 | }; 59 | context.main(); 60 | println!(); 61 | } 62 | 63 | panic!("A total of {} verification tests failed", context.count) 64 | } 65 | } 66 | 67 | #[rustfmt::skip] 68 | impl Context { 69 | fn main(&mut self) { 70 | let mut root = interact::RootSend::new(); 71 | let mut root_local = interact::RootLocal::new(); 72 | let seed = 42; 73 | let mut rng: rand::StdRng = rand::SeedableRng::seed_from_u64(seed); 74 | 75 | root.owned.insert("complex", Box::new(Complex::new_random(&mut rng))); 76 | root.owned.insert("basic", Box::new(Basic::new_random(&mut rng))); 77 | root_local.owned.insert("rc_loops", Box::new(LocalRcLoop::new_random(&mut rng))); 78 | 79 | let mut root = interact::Root { 80 | send: Some(&mut root), 81 | local: Some(&mut root_local), 82 | }; 83 | 84 | // Check for a non-existing root key 85 | 86 | verify!(self, root.access("not_existing") => "(Err(MissingStartComponent), Assist { valid: 0, pending: 0, pending_special: 0, next_options: Avail(0, []) })"); 87 | 88 | // Check for a basic read query 89 | 90 | verify!(self, root.access("basic.u_16") => "(Ok(NodeTree { info: Leaf(\"50158\"), meta: Some(Wrap(1)), size: 6 }), Assist { valid: 10, pending: 0, pending_special: 0, next_options: Avail(0, []) })"); 91 | verify!(self, root.access("basic.u_") => "(Err(UnexpectedToken), Assist { valid: 5, pending: 3, pending_special: 0, next_options: Avail(1, [\"u_s\", \"u_64\", \"u_32\", \"u_16\", \"u_8\"]) })"); 92 | 93 | // Basic assignment check 94 | 95 | verify!(self, root.access("basic.u_64 = 1234") => "(Ok(NodeTree { info: Leaf(\"\"), meta: None, size: 1 }), Assist { valid: 17, pending: 0, pending_special: 0, next_options: Avail(0, []) })"); 96 | verify!(self, root.access("basic.u_64") => "(Ok(NodeTree { info: Leaf(\"1234\"), meta: Some(Wrap(1)), size: 5 }), Assist { valid: 10, pending: 0, pending_special: 0, next_options: Avail(0, []) })"); 97 | 98 | // Token parsing error 99 | 100 | verify!(self, root.access("state.complex.0.0.0.0 = 100000000000000000001") => "(Err(TokenError(IntError(ParseIntError { kind: Overflow }))), Assist { valid: 0, pending: 0, pending_special: 0, next_options: Avail(0, []) })"); 101 | 102 | // Verify calling immutable methods from prompt 103 | 104 | verify!(self, root.access("complex.tuple.0.0 = 3") => "(Ok(NodeTree { info: Leaf(\"\"), meta: None, size: 1 }), Assist { valid: 21, pending: 0, pending_special: 0, next_options: Avail(0, []) })"); 105 | verify!(self, root.access("complex.tuple_1.0 = 3") => "(Ok(NodeTree { info: Leaf(\"\"), meta: None, size: 1 }), Assist { valid: 21, pending: 0, pending_special: 0, next_options: Avail(0, []) })"); 106 | verify!(self, root.access("complex.check()") => "(Ok(NodeTree { info: Leaf(\"true\"), meta: Some(Wrap(1)), size: 5 }), Assist { valid: 15, pending: 0, pending_special: 0, next_options: Avail(0, []) })"); 107 | verify!(self, root.access("complex.tuple_1.0 = 4") => "(Ok(NodeTree { info: Leaf(\"\"), meta: None, size: 1 }), Assist { valid: 21, pending: 0, pending_special: 0, next_options: Avail(0, []) })"); 108 | verify!(self, root.access("complex.check()") => "(Ok(NodeTree { info: Leaf(\"false\"), meta: Some(Wrap(1)), size: 6 }), Assist { valid: 15, pending: 0, pending_special: 0, next_options: Avail(0, []) })"); 109 | verify!(self, root.access("complex.add(3)") => "(Ok(NodeTree { info: Leaf(\"()\"), meta: Some(Wrap(1)), size: 3 }), Assist { valid: 14, pending: 0, pending_special: 0, next_options: Avail(0, []) })"); 110 | verify!(self, root.access("complex.tuple_1.0 = 7") => "(Ok(NodeTree { info: Leaf(\"\"), meta: None, size: 1 }), Assist { valid: 21, pending: 0, pending_special: 0, next_options: Avail(0, []) })"); 111 | 112 | // TODO: add more comparision tests 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /interact/src/access/vec.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::access::{Access, ImmutAccess, MutAccess, Reflect, ReflectDirect, ReflectMut}; 4 | use crate::climber::{ClimbError, Climber}; 5 | use crate::deser::Deser; 6 | use crate::node_tree::NodeTree; 7 | use crate::reflector::Reflector; 8 | 9 | pub trait ReflectVec { 10 | fn get_len(&self) -> usize; 11 | fn get_item(&self, idx: usize) -> Option<&dyn Access>; 12 | fn get_item_mut(&mut self, _idx: usize) -> Option<&mut dyn Access>; 13 | } 14 | 15 | macro_rules! if_mut { 16 | (mut, {$t: expr} else {$f:expr}) => { 17 | $t 18 | }; 19 | (immut, {$t: expr} else {$f:expr}) => { 20 | $f 21 | }; 22 | } 23 | 24 | macro_rules! sized_iter { 25 | ($t:ty, $i:ident, $name:expr) => { 26 | impl ReflectVec for $t 27 | where 28 | T: Access, 29 | { 30 | fn get_len(&self) -> usize { 31 | self.len() 32 | } 33 | 34 | fn get_item(&self, idx: usize) -> Option<&dyn Access> { 35 | if idx >= self.len() { 36 | None 37 | } else { 38 | Some(&self[idx]) 39 | } 40 | } 41 | 42 | fn get_item_mut(&mut self, _idx: usize) -> Option<&mut dyn Access> { 43 | if_mut! { 44 | $i, { 45 | if _idx >= self.len() { 46 | None 47 | } else { 48 | Some(&mut self[_idx]) 49 | } 50 | } else { 51 | None 52 | } 53 | } 54 | } 55 | } 56 | 57 | impl ReflectDirect for $t 58 | where 59 | T: Access, 60 | { 61 | fn immut_reflector(&self, reflector: &Arc) -> NodeTree { 62 | Reflector::reflect_vec(reflector, self, $name) 63 | } 64 | 65 | fn immut_climber<'a>( 66 | &self, 67 | climber: &mut Climber<'a>, 68 | ) -> Result, ClimbError> { 69 | if !climber.open_bracket() { 70 | return Ok(None); 71 | } 72 | 73 | let v = match usize::deser(&mut climber.borrow_tracker()) { 74 | Err(e) => Err(e), 75 | Ok(i) => Ok(self.get_item(i)), 76 | }; 77 | 78 | let v = match v { 79 | Ok(None) => return Err(ClimbError::NotFound), 80 | Err(err) => return Err(ClimbError::DeserError(err)), 81 | Ok(Some(v)) => v, 82 | }; 83 | 84 | climber.close_bracket()?; 85 | 86 | return climber.general_access_immut(v).map(Some); 87 | } 88 | 89 | fn mut_climber<'a>( 90 | &mut self, 91 | climber: &mut Climber<'a>, 92 | ) -> Result, ClimbError> { 93 | if !climber.open_bracket() { 94 | return Ok(None); 95 | } 96 | 97 | let v = match usize::deser(&mut climber.borrow_tracker()) { 98 | Err(e) => Err(e), 99 | Ok(i) => Ok(self.get_item_mut(i)), 100 | }; 101 | 102 | let v = match v { 103 | Ok(None) => return Err(ClimbError::NotFound), 104 | Err(err) => return Err(ClimbError::DeserError(err)), 105 | Ok(Some(v)) => v, 106 | }; 107 | 108 | climber.close_bracket()?; 109 | 110 | return climber.general_access_mut(v).map(Some); 111 | } 112 | } 113 | 114 | impl Access for $t 115 | where 116 | T: Access, 117 | { 118 | fn immut_access(&self) -> ImmutAccess { 119 | ImmutAccess::no_funcs(Reflect::Direct(self)) 120 | } 121 | 122 | fn mut_access(&mut self) -> MutAccess { 123 | if_mut! { 124 | $i, { 125 | MutAccess::no_funcs(ReflectMut::Direct(self)) 126 | } else { 127 | MutAccess::no_funcs(ReflectMut::Immutable) 128 | } 129 | } 130 | } 131 | } 132 | }; 133 | } 134 | 135 | sized_iter!(&[T], immut, ""); 136 | sized_iter!(&mut [T], mut, ""); 137 | sized_iter!(Vec, mut, "Vec"); 138 | sized_iter!([T; 1], mut, ""); 139 | sized_iter!([T; 2], mut, ""); 140 | sized_iter!([T; 3], mut, ""); 141 | sized_iter!([T; 4], mut, ""); 142 | sized_iter!([T; 5], mut, ""); 143 | sized_iter!([T; 6], mut, ""); 144 | sized_iter!([T; 7], mut, ""); 145 | sized_iter!([T; 8], mut, ""); 146 | sized_iter!([T; 9], mut, ""); 147 | sized_iter!([T; 10], mut, ""); 148 | sized_iter!([T; 11], mut, ""); 149 | sized_iter!([T; 12], mut, ""); 150 | sized_iter!([T; 13], mut, ""); 151 | sized_iter!([T; 14], mut, ""); 152 | sized_iter!([T; 15], mut, ""); 153 | sized_iter!([T; 16], mut, ""); 154 | sized_iter!([T; 17], mut, ""); 155 | sized_iter!([T; 18], mut, ""); 156 | sized_iter!([T; 19], mut, ""); 157 | sized_iter!([T; 21], mut, ""); 158 | sized_iter!([T; 22], mut, ""); 159 | sized_iter!([T; 23], mut, ""); 160 | sized_iter!([T; 24], mut, ""); 161 | sized_iter!([T; 25], mut, ""); 162 | sized_iter!([T; 27], mut, ""); 163 | sized_iter!([T; 28], mut, ""); 164 | sized_iter!([T; 29], mut, ""); 165 | sized_iter!([T; 31], mut, ""); 166 | sized_iter!([T; 32], mut, ""); 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Interact   [![Build Status]][travis] [![Latest Version]][crates.io] [![Docs badge]][Docs link] [![License badge]][License link] 2 | 3 | [Build Status]: https://api.travis-ci.org/interact-rs/interact.svg?branch=master 4 | [travis]: https://travis-ci.org/interact-rs/interact 5 | [Latest Version]: https://img.shields.io/crates/v/interact.svg 6 | [crates.io]: https://crates.io/crates/interact 7 | [License badge]: https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg 8 | [License link]: https://travis-ci.org/interact-rs/interact 9 | [Docs badge]: https://docs.rs/interact/badge.svg 10 | [Docs link]: https://docs.rs/interact 11 | 12 | **Interact is a framework for friendly online introspection of the running program state in an intuitive command-line *interact*ive way.** 13 | 14 | You may be looking for: 15 | 16 | * [Book](https://interact-rs.github.io/interact/book/) (master). 17 | * Crate docs of `interact_prompt`: [latest](https://docs.rs/interact_prompt), [master](https://interact-rs.github.io/interact/doc/interact_prompt/index.html) 18 | * Crate docs of `interact`: [latest](https://docs.rs/interact), [master](https://interact-rs.github.io/interact/doc/interact/index.html) 19 | 20 | --- 21 | 22 | Interact is useful for server programs that otherwise receive no input. You can use Interact to make your server receive commands using the special prompt from the `interact_prompt` crate. The commands can be used to browse your server's internal state, modify it, and call methods that were specified in `interact` derive attributes. 23 | 24 | Interact is implemented for stable Rust, using only safe mode. 25 | 26 | ## Introduction 27 | 28 | While dynamically-typed interpreted languages offer the advantage of being able to look at a running program state using a prompt, compiled languages often do not provide that feature. Being hard as it is to introduce interpreters into compiled languages, the Interact project aims to provide a midway solution using stable Rust. 29 | 30 | ## How to make your server Interact-able 31 | 32 | * Custom-derive types using `#[derive(Interact)]`. 33 | * Use `#[interact(skip)]` for problematic fields. 34 | * No need to worry about `Rc`, `RefCell`, `Arc`, or `Mutex`, even with reference loops. Handling for that exists, as demonstrated later. 35 | * Register process-global or TLS-local state via `interact_prompt`'s registry. 36 | * Invoke `interact_prompt` either directly or in its own OS thread (async not supported yet). 37 | 38 | ## Interact Prompt features 39 | 40 | * Provide Rust-like expressions to explore from the root nodes, e.g. `node.some_map["value"].field.sub_field`. 41 | * Full auto-complete and completion hints for type names, field names, enum names, function names, and punctuation. 42 | * Modify the state from the prompt: at places where mutable access is possible at compile-time, you can assign to fields of inner structs at run-time by appending `= `. 43 | * It is possible to call methods that were linked using special `interact` attributes. 44 | * State prints have an adjustable limit - if the state is too big it can be automatically capped so your terminal is not overwhelmed. 45 | * Reference cycles (via `Rc` or otherwise) are handled gracefully in reflected values - a unique number is printed at all the common sites: the first encounter and the repeats. 46 | * Data indirection is supported - for example, Actix's `Addr` can be traversed into, exposing the full server state (see the [example in the book](https://interact-rs.github.io/interact/book/examples/actix.html)). 47 | 48 | ## Interact mini-example with a recorded demo 49 | 50 | The program below registers states and invokes the Interact prompt on the main thread. 51 | 52 | ```rust 53 | extern crate interact; 54 | 55 | use interact::Interact; 56 | use interact_prompt::{LocalRegistry, Settings}; 57 | use std::{cell::RefCell, rc::Rc}; 58 | 59 | #[derive(Interact)] 60 | struct Point { 61 | x: i32, 62 | y: i32, 63 | } 64 | 65 | #[derive(Interact)] 66 | struct State { 67 | maybe_point: Option, 68 | complex: ((((usize, usize), u32, (u32, (u32,))), u32), u32), 69 | behind_rc: Rc>, 70 | behind_rc2: Rc>, 71 | } 72 | 73 | fn main() -> Result<(), interact_prompt::PromptError> { 74 | let rc = Rc::new(RefCell::new(3)); 75 | let state = State { 76 | maybe_point: Some(Point { x: 3, y: 3 }), 77 | complex: ((((0, 0), 0, (0, (0,))), 0), 0), 78 | behind_rc: rc.clone(), 79 | behind_rc2: rc, 80 | }; 81 | 82 | LocalRegistry::insert("state", Box::new(state)); 83 | interact_prompt::direct(Settings::default(), ())?; 84 | Ok(()) 85 | } 86 | ``` 87 | 88 | (this is just one mode for using the Interact prompt. Another mode is running it in the background allowing it to traverse, access, and modify the global process state safely and without interference). 89 | 90 | When cloning this repository, it can be run using `cargo run --example mini-example`. Here's a recorded session: 91 | 92 |

93 | 94 |

95 | 96 | ## Getting help 97 | 98 | You are more than welcome to browse and open new issues! 99 | 100 | [issues]: https://github.com/interact-rs/interact/issues/new/choose 101 | 102 | ## License 103 | 104 | Interact is licensed under either of 105 | 106 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 107 | http://www.apache.org/licenses/LICENSE-2.0) 108 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or 109 | http://opensource.org/licenses/MIT) 110 | 111 | at your option. 112 | 113 | ### Contribution 114 | 115 | Unless you explicitly state otherwise, any contribution intentionally submitted 116 | for inclusion in Interact by you, as defined in the Apache-2.0 license, shall be 117 | dual-licensed as above, without any additional terms or conditions. 118 | -------------------------------------------------------------------------------- /interact/src/deser/basic.rs: -------------------------------------------------------------------------------- 1 | use crate::deser::{Deser, DeserError, Result, Tracker}; 2 | use crate::tokens::{Token, TokenKind}; 3 | 4 | macro_rules! impl_unsigned { 5 | ($a:tt) => { 6 | impl Deser for $a { 7 | fn deser<'a, 'b>(tracker: &mut Tracker<'a, 'b>) -> Result { 8 | if !tracker.has_remaining() { 9 | return Err(DeserError::EndOfTokenList); 10 | } 11 | 12 | if let TokenKind::NonNegativeDecimal(nnd) = tracker.top_kind() { 13 | let nnd = nnd.clone(); 14 | if nnd > u64::from(Self::max_value()) { 15 | return Err(DeserError::NumberTooLarge); 16 | } 17 | 18 | tracker.step(); 19 | return Ok(nnd as Self); 20 | } 21 | if let TokenKind::Decimal(nnd) = tracker.top_kind() { 22 | let nnd = nnd.clone(); 23 | if nnd < 0 { 24 | return Err(DeserError::NumberTooSmall); 25 | } 26 | if nnd as u64 > u64::from(Self::max_value()) { 27 | return Err(DeserError::NumberTooLarge); 28 | } 29 | 30 | tracker.step(); 31 | return Ok(nnd as Self); 32 | } 33 | 34 | Err(DeserError::UnexpectedToken) 35 | } 36 | } 37 | }; 38 | } 39 | 40 | impl_unsigned!(u64); 41 | impl_unsigned!(u32); 42 | impl_unsigned!(u16); 43 | impl_unsigned!(u8); 44 | 45 | macro_rules! impl_signed { 46 | ($a:tt) => { 47 | impl Deser for $a { 48 | fn deser<'a, 'b>(tracker: &mut Tracker<'a, 'b>) -> Result { 49 | if !tracker.has_remaining() { 50 | return Err(DeserError::EndOfTokenList); 51 | } 52 | 53 | if let TokenKind::NonNegativeDecimal(nnd) = tracker.top_kind() { 54 | let nnd = nnd.clone(); 55 | if nnd >= Self::max_value() as u64 { 56 | return Err(DeserError::NumberTooLarge); 57 | } 58 | tracker.step(); 59 | return Ok(nnd as Self); 60 | } else if let TokenKind::Decimal(dec) = tracker.top_kind() { 61 | let dec = dec.clone(); 62 | if dec < Self::min_value() as i64 { 63 | return Err(DeserError::NumberTooSmall); 64 | } 65 | if dec > Self::max_value() as i64 { 66 | return Err(DeserError::NumberTooLarge); 67 | } 68 | 69 | tracker.step(); 70 | return Ok(dec as Self); 71 | } 72 | 73 | Err(DeserError::UnexpectedToken) 74 | } 75 | } 76 | }; 77 | } 78 | 79 | impl_signed!(isize); 80 | impl_signed!(i64); 81 | impl_signed!(i32); 82 | impl_signed!(i16); 83 | impl_signed!(i8); 84 | 85 | impl Deser for usize { 86 | fn deser<'a, 'b>(tracker: &mut Tracker<'a, 'b>) -> Result { 87 | if !tracker.has_remaining() { 88 | return Err(DeserError::EndOfTokenList); 89 | } 90 | 91 | if let TokenKind::NonNegativeDecimal(nnd) = tracker.top_kind() { 92 | let nnd = nnd.clone(); 93 | if nnd > Self::max_value() as u64 { 94 | return Err(DeserError::NumberTooLarge); 95 | } 96 | 97 | tracker.step(); 98 | return Ok(nnd as Self); 99 | } 100 | if let TokenKind::Decimal(nnd) = tracker.top_kind() { 101 | let nnd = nnd.clone(); 102 | if nnd < 0 { 103 | return Err(DeserError::NumberTooSmall); 104 | } 105 | if nnd as u64 > Self::max_value() as u64 { 106 | return Err(DeserError::NumberTooLarge); 107 | } 108 | 109 | tracker.step(); 110 | return Ok(nnd as Self); 111 | } 112 | 113 | Err(DeserError::UnexpectedToken) 114 | } 115 | } 116 | 117 | macro_rules! impl_simple { 118 | ($a:tt, $token:ident) => { 119 | impl Deser for $a { 120 | fn deser<'a, 'b>(tracker: &mut Tracker<'a, 'b>) -> Result { 121 | if !tracker.has_remaining() { 122 | return Err(DeserError::EndOfTokenList); 123 | } 124 | 125 | if let TokenKind::$token(s) = tracker.top_kind() { 126 | let s = s.clone(); 127 | tracker.step(); 128 | return Ok(s); 129 | } 130 | 131 | Err(DeserError::UnexpectedToken) 132 | } 133 | } 134 | }; 135 | } 136 | 137 | impl_simple!(String, String); 138 | impl_simple!(char, Char); 139 | 140 | impl Deser for bool { 141 | fn deser<'a, 'b>(tracker: &mut Tracker<'a, 'b>) -> Result { 142 | let values = [("false", false), ("true", true)]; 143 | 144 | if !tracker.has_remaining() { 145 | for (s, _) in values.iter() { 146 | tracker.possible_token(Token::new_borrowed(TokenKind::Ident, s)); 147 | } 148 | return Err(DeserError::EndOfTokenList); 149 | } 150 | 151 | if let TokenKind::Ident = tracker.top_kind() { 152 | for (s, value) in values.iter() { 153 | if *s == tracker.top().text { 154 | tracker.step(); 155 | return Ok(*value); 156 | } 157 | } 158 | 159 | for (s, _) in values.iter() { 160 | if s.starts_with(tracker.top().text.as_ref()) { 161 | tracker.possible_token(Token::new_borrowed(TokenKind::Ident, s)); 162 | } 163 | } 164 | } 165 | 166 | Err(DeserError::UnexpectedToken) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /interact/src/access/tuple.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::sync::Arc; 3 | 4 | use crate::access::{ 5 | derive::{ReflectStruct, Struct, StructKind}, 6 | Access, AssignError, ImmutAccess, MutAccess, Reflect, ReflectDirect, ReflectMut, 7 | }; 8 | use crate::climber::{ClimbError, Climber, EnumOrStruct, EnumOrStructMut}; 9 | use crate::deser::{self, Deser}; 10 | use crate::node_tree::{NodeInfo, NodeTree}; 11 | use crate::reflector::Reflector; 12 | 13 | macro_rules! tuple { 14 | ($count:expr; { $(($n:ident, $i:tt)),* }) => { 15 | 16 | impl<$($n),*> ReflectStruct for ($($n),*) 17 | where $($n : Access),* 18 | { 19 | fn get_desc(&self) -> Struct { 20 | Struct { 21 | name: "", 22 | kind: StructKind::Tuple($count), 23 | } 24 | } 25 | 26 | fn get_field_by_name(&self, _: &'static str) -> Option<&dyn Access> { 27 | None 28 | } 29 | 30 | fn get_field_by_idx(&self, idx: usize) -> Option<&dyn Access> { 31 | $(if idx == $i { return Some(&self.$i) });* 32 | None 33 | } 34 | 35 | fn get_field_by_name_mut(&mut self, _: &'static str) -> Option<&mut dyn Access> { 36 | None 37 | } 38 | 39 | fn get_field_by_idx_mut(&mut self, idx: usize) -> Option<&mut dyn Access> { 40 | $(if idx == $i { return Some(&mut self.$i) });* 41 | None 42 | } 43 | } 44 | 45 | impl<$($n),*> ReflectDirect for ($($n),*) 46 | where $($n : Access),* 47 | { 48 | fn immut_reflector(&self, reflector: &Arc) -> NodeTree { 49 | Reflector::reflect_struct(reflector, &self.get_desc(), self, true) 50 | } 51 | 52 | fn immut_climber<'a>( 53 | &self, 54 | climber: &mut Climber<'a>, 55 | ) -> Result, ClimbError> { 56 | climber.check_field_access_immut(&EnumOrStruct::Struct(self)) 57 | } 58 | 59 | fn mut_climber<'a>( 60 | &mut self, 61 | climber: &mut Climber<'a>, 62 | ) -> Result, ClimbError> { 63 | climber.check_field_access_mut(EnumOrStructMut::Struct(self)) 64 | } 65 | } 66 | 67 | impl<$($n),*> Access for ($($n),*) 68 | where $($n : Access + Deser),* 69 | { 70 | fn immut_access(&self) -> ImmutAccess { 71 | ImmutAccess::no_funcs(Reflect::Direct(self)) 72 | } 73 | 74 | fn mut_access(&mut self) -> MutAccess { 75 | MutAccess::no_funcs(ReflectMut::Direct(self)) 76 | } 77 | 78 | mut_assign_deser!(); 79 | } 80 | } 81 | } 82 | 83 | impl
ReflectStruct for (A,) 84 | where 85 | A: Access, 86 | { 87 | fn get_desc(&self) -> Struct { 88 | Struct { 89 | name: "", 90 | kind: StructKind::Tuple(1), 91 | } 92 | } 93 | 94 | fn get_field_by_name(&self, _: &'static str) -> Option<&dyn Access> { 95 | None 96 | } 97 | 98 | fn get_field_by_idx(&self, idx: usize) -> Option<&dyn Access> { 99 | if idx == 0 { 100 | return Some(&self.0); 101 | } 102 | None 103 | } 104 | 105 | fn get_field_by_name_mut(&mut self, _: &'static str) -> Option<&mut dyn Access> { 106 | None 107 | } 108 | 109 | fn get_field_by_idx_mut(&mut self, idx: usize) -> Option<&mut dyn Access> { 110 | if idx == 0 { 111 | return Some(&mut self.0); 112 | } 113 | None 114 | } 115 | } 116 | 117 | impl ReflectDirect for (T,) 118 | where 119 | T: Access, 120 | { 121 | fn immut_reflector(&self, reflector: &Arc) -> NodeTree { 122 | Reflector::reflect_struct(reflector, &self.get_desc(), self, true) 123 | } 124 | 125 | fn immut_climber<'a>(&self, climber: &mut Climber<'a>) -> Result, ClimbError> { 126 | climber.check_field_access_immut(&EnumOrStruct::Struct(self)) 127 | } 128 | 129 | fn mut_climber<'a>( 130 | &mut self, 131 | climber: &mut Climber<'a>, 132 | ) -> Result, ClimbError> { 133 | climber.check_field_access_mut(EnumOrStructMut::Struct(self)) 134 | } 135 | } 136 | 137 | impl Access for (A,) 138 | where 139 | A: Access + Deser, 140 | { 141 | fn immut_access(&self) -> ImmutAccess { 142 | ImmutAccess::no_funcs(Reflect::Direct(self)) 143 | } 144 | 145 | fn mut_access(&mut self) -> MutAccess { 146 | MutAccess::no_funcs(ReflectMut::Direct(self)) 147 | } 148 | 149 | mut_assign_deser!(); 150 | } 151 | 152 | impl ReflectDirect for () { 153 | fn immut_reflector(&self, reflector: &Arc) -> NodeTree { 154 | let obj_ptr = ((self as *const _) as usize, 0); 155 | let meta = match Reflector::seen_ptr(reflector, obj_ptr) { 156 | Ok(v) => return v, 157 | Err(meta) => meta, 158 | }; 159 | NodeInfo::Leaf(Cow::Borrowed("()")).with_meta(meta) 160 | } 161 | 162 | fn immut_climber<'a>( 163 | &self, 164 | _climber: &mut Climber<'a>, 165 | ) -> Result, ClimbError> { 166 | Ok(None) 167 | } 168 | 169 | fn mut_climber<'a>( 170 | &mut self, 171 | _climber: &mut Climber<'a>, 172 | ) -> Result, ClimbError> { 173 | Ok(None) 174 | } 175 | } 176 | 177 | impl Access for () { 178 | fn immut_access(&self) -> ImmutAccess { 179 | ImmutAccess::no_funcs(Reflect::Direct(self)) 180 | } 181 | 182 | fn mut_access(&mut self) -> MutAccess { 183 | MutAccess::no_funcs(ReflectMut::Direct(self)) 184 | } 185 | 186 | mut_assign_deser!(); 187 | } 188 | 189 | tuple!(2; {(A, 0), (B, 1)}); 190 | tuple!(3; {(A, 0), (B, 1), (C, 2)}); 191 | tuple!(4; {(A, 0), (B, 1), (C, 2), (D, 3)}); 192 | tuple!(5; {(A, 0), (B, 1), (C, 2), (D, 3), (E, 4)}); 193 | tuple!(6; {(A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5)}); 194 | tuple!(7; {(A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6)}); 195 | tuple!(8; {(A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6), (H, 7)}); 196 | tuple!(9; {(A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6), (H, 7), (I, 8)}); 197 | tuple!(10; {(A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6), (H, 7), (I, 8), (J, 9)}); 198 | -------------------------------------------------------------------------------- /interact/src/util/node_tree.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::io::Cursor; 3 | use std::io::Write; 4 | use std::ops::Deref; 5 | use std::sync::atomic::AtomicUsize; 6 | use std::sync::mpsc::Receiver; 7 | use std::sync::Arc; 8 | 9 | type Delimiter = char; 10 | 11 | #[derive(Debug)] 12 | pub enum NodeInfo { 13 | Grouped(char, Box, char), 14 | Delimited(Delimiter, Vec), 15 | Named(Box, Box), 16 | Tuple(Box, &'static str, Box), 17 | Leaf(Cow<'static, str>), 18 | Hole(Box>), 19 | BorrowedMut, 20 | Locked, 21 | Repeated, 22 | Limited, 23 | } 24 | 25 | pub type PtrMeta = Arc; 26 | 27 | #[derive(Debug)] 28 | pub struct Wrap(pub PtrMeta); 29 | 30 | /// NodeTree represent a reflection of an Interact type that implemented the `Access` trait. It may 31 | /// be a partial reflection due to limits and indirections (see `Reflector`). 32 | #[derive(Debug)] 33 | pub struct NodeTree { 34 | pub info: NodeInfo, 35 | pub meta: Option, 36 | pub size: usize, 37 | } 38 | 39 | impl NodeTree { 40 | pub fn new(info: NodeInfo, meta: Option) -> Self { 41 | Self { 42 | info, 43 | meta, 44 | size: 0, 45 | } 46 | } 47 | } 48 | 49 | impl Eq for Wrap {} 50 | impl PartialEq for Wrap { 51 | fn eq(&self, other: &Self) -> bool { 52 | Arc::ptr_eq(&self.0, &other.0) 53 | } 54 | } 55 | 56 | struct Printer { 57 | accum: Cursor>, 58 | } 59 | 60 | impl Printer { 61 | fn write(&mut self, s: &str) -> Result<(), std::io::Error> { 62 | self.accum.write_all(s.as_bytes())?; 63 | Ok(()) 64 | } 65 | } 66 | 67 | impl NodeInfo { 68 | pub fn into_node(self) -> NodeTree { 69 | NodeTree { 70 | info: self, 71 | meta: None, 72 | size: 0, 73 | } 74 | } 75 | 76 | pub fn with_meta(self, ptr_meta: PtrMeta) -> NodeTree { 77 | NodeTree { 78 | info: self, 79 | meta: Some(Wrap(ptr_meta)), 80 | size: 0, 81 | } 82 | } 83 | 84 | pub fn named(name: &'static str, a_self: NodeTree) -> Self { 85 | NodeInfo::Named( 86 | Box::new(NodeTree { 87 | info: NodeInfo::Leaf(Cow::Borrowed(name)), 88 | meta: None, 89 | size: 0, 90 | }), 91 | Box::new(a_self), 92 | ) 93 | } 94 | 95 | pub fn format(&self) -> Result, std::io::Error> { 96 | let mut state = Printer { 97 | accum: Cursor::new(Vec::new()), 98 | }; 99 | 100 | self.inner_pretty_print(&mut state)?; 101 | 102 | let Printer { accum, .. } = state; 103 | 104 | Ok(accum.into_inner()) 105 | } 106 | 107 | fn inner_pretty_print(&self, state: &mut Printer) -> Result<(), std::io::Error> { 108 | use crate::NodeInfo::*; 109 | 110 | match self { 111 | Grouped(prefix, sub, end) => { 112 | let space = match sub.deref().info { 113 | Delimited(_, ref v) if v.is_empty() => "", 114 | _ => " ", 115 | }; 116 | 117 | state.write(&format!("{}{}", prefix, space))?; 118 | sub.info.inner_pretty_print(state)?; 119 | state.write(&format!("{}{}", space, end))?; 120 | } 121 | Delimited(delimiter, v) => { 122 | for (idx, i) in v.iter().enumerate() { 123 | if idx > 0 { 124 | state.write(&format!("{} ", delimiter))?; 125 | } 126 | 127 | i.info.inner_pretty_print(state)?; 128 | } 129 | } 130 | Tuple(key, sep, value) => { 131 | key.info.inner_pretty_print(state)?; 132 | state.write(&format!(" {} ", sep))?; 133 | value.info.inner_pretty_print(state)?; 134 | } 135 | Named(item, next) => { 136 | item.info.inner_pretty_print(state)?; 137 | state.write(&" ")?; 138 | next.info.inner_pretty_print(state)?; 139 | } 140 | Leaf(s) => { 141 | state.write(s)?; 142 | } 143 | Hole(_) => { 144 | state.write(&format!(""))?; 145 | } 146 | BorrowedMut => { 147 | state.write(&format!(""))?; 148 | } 149 | Locked => { 150 | state.write(&format!(""))?; 151 | } 152 | Limited => { 153 | state.write("...")?; 154 | } 155 | Repeated => { 156 | state.write(&format!(""))?; 157 | } 158 | }; 159 | 160 | Ok(()) 161 | } 162 | } 163 | 164 | impl NodeTree { 165 | pub fn resolve(&mut self) -> usize { 166 | use crate::NodeInfo::*; 167 | 168 | loop { 169 | let mut count = 1; 170 | 171 | let r = match &mut self.info { 172 | Grouped(_, sub, _) => { 173 | count += 2 + sub.resolve(); 174 | None 175 | } 176 | Delimited(_, v) => { 177 | for i in v.iter_mut() { 178 | count += i.resolve() + 2; 179 | } 180 | None 181 | } 182 | Tuple(key, sep, value) => { 183 | count += key.resolve(); 184 | count += sep.len() + 2; 185 | count += value.resolve(); 186 | None 187 | } 188 | Named(item, next) => { 189 | count += item.resolve(); 190 | count += next.resolve(); 191 | None 192 | } 193 | Leaf(v) => { 194 | count += v.len(); 195 | None 196 | } 197 | Hole(receiver) => Some((*receiver).recv().unwrap()), 198 | Limited => None, 199 | Repeated => None, 200 | BorrowedMut => None, 201 | Locked => None, 202 | }; 203 | 204 | if let Some(r) = r { 205 | *self = r; 206 | continue; 207 | } 208 | 209 | self.size = count; 210 | 211 | return count; 212 | } 213 | } 214 | } 215 | 216 | use std::fmt; 217 | 218 | impl fmt::Display for NodeInfo { 219 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 220 | let v = self.format().unwrap(); 221 | 222 | write!(f, "{}", String::from_utf8_lossy(v.as_slice())) 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /interact_prompt/src/print.rs: -------------------------------------------------------------------------------- 1 | use ansi_term::Color; 2 | use interact::NodeTree; 3 | use std::collections::HashMap; 4 | use std::sync::atomic::Ordering; 5 | 6 | pub struct NodePrinterSettings { 7 | pub max_line_length: u16, 8 | pub indent_step: u16, 9 | } 10 | 11 | struct Printer<'a> { 12 | settings: &'a NodePrinterSettings, 13 | indent: usize, 14 | indent_string: String, 15 | line_used: usize, 16 | item_linebreak: bool, 17 | seen: HashMap, 18 | seen_idx: usize, 19 | } 20 | 21 | impl<'a> Printer<'a> { 22 | fn write(&mut self, s: &str) { 23 | if self.line_used == 0 { 24 | print!("{}", self.indent_string); 25 | } 26 | print!("{}", s); 27 | self.line_used += s.len(); 28 | } 29 | 30 | fn end_line(&mut self) { 31 | println!(); 32 | self.line_used = 0; 33 | } 34 | 35 | fn up_indent(&mut self) { 36 | self.indent += self.settings.indent_step as usize; 37 | self.indent_string = " ".repeat(self.indent); 38 | } 39 | 40 | fn down_indent(&mut self) { 41 | self.indent -= self.settings.indent_step as usize; 42 | self.indent_string = " ".repeat(self.indent); 43 | } 44 | 45 | fn inner_pretty_print(&mut self, elem: &NodeTree) { 46 | use interact::NodeInfo::*; 47 | 48 | let mut repeated_idx = None; 49 | if let Some(ptr_meta) = &elem.meta { 50 | use std::collections::hash_map::Entry; 51 | 52 | use std::borrow::Borrow; 53 | let b = (*ptr_meta.0).borrow(); 54 | let arc_ptr = (b as *const _) as usize; 55 | 56 | if let Repeated = &elem.info { 57 | repeated_idx = Some(match self.seen.entry(arc_ptr) { 58 | Entry::Occupied(entry) => *entry.get(), 59 | Entry::Vacant(entry) => { 60 | let idx = self.seen_idx; 61 | entry.insert(idx); 62 | self.seen_idx += 1; 63 | idx 64 | } 65 | }); 66 | } else { 67 | let nr_refs = ptr_meta.0.load(Ordering::Relaxed); 68 | 69 | if nr_refs >= 2 { 70 | let seen_idx = match self.seen.entry(arc_ptr) { 71 | Entry::Occupied(entry) => *entry.get(), 72 | Entry::Vacant(entry) => { 73 | let idx = self.seen_idx; 74 | entry.insert(idx); 75 | self.seen_idx += 1; 76 | idx 77 | } 78 | }; 79 | 80 | self.write(&format!( 81 | "{}", 82 | Color::Green.paint(format!("[#{}] ", seen_idx)) 83 | )); 84 | } 85 | } 86 | } 87 | 88 | match &elem.info { 89 | Grouped(prefix, sub, end) => { 90 | self.write(&format!("{}", Color::Cyan.paint(format!("{}", prefix)))); 91 | let item_linebreak = self.item_linebreak; 92 | let mut indented = false; 93 | 94 | let has_space = if let Delimited(_, ref v) = &sub.info { 95 | !v.is_empty() && prefix != &'(' 96 | } else { 97 | true 98 | }; 99 | 100 | if self.indent + elem.size > self.settings.max_line_length as usize { 101 | self.item_linebreak = true; 102 | self.up_indent(); 103 | self.end_line(); 104 | indented = true; 105 | } else { 106 | self.item_linebreak = false; 107 | if has_space { 108 | self.write(&" "); 109 | } 110 | } 111 | 112 | self.inner_pretty_print(&sub); 113 | 114 | self.item_linebreak = item_linebreak; 115 | 116 | if indented { 117 | self.down_indent(); 118 | self.end_line(); 119 | } else if has_space { 120 | self.write(&" "); 121 | } 122 | 123 | self.write(&format!("{}", Color::Cyan.paint(&format!("{}", end)))); 124 | } 125 | Delimited(delimiter, v) => { 126 | for (idx, i) in v.iter().enumerate() { 127 | if idx > 0 { 128 | if self.item_linebreak { 129 | self.write(&format!("{}", delimiter)); 130 | self.end_line(); 131 | } else { 132 | self.write(&format!("{} ", delimiter)); 133 | } 134 | } 135 | 136 | self.inner_pretty_print(&i); 137 | } 138 | } 139 | Tuple(key, sep, value) => { 140 | self.inner_pretty_print(key); 141 | self.write(&format!("{} ", Color::Cyan.paint(*sep))); 142 | self.inner_pretty_print(value); 143 | } 144 | Named(item, next) => { 145 | self.inner_pretty_print(item); 146 | self.write(&" "); 147 | self.inner_pretty_print(next); 148 | } 149 | Limited => { 150 | self.write(&format!("{}", Color::Yellow.bold().paint("...<<<>>>..."))); 151 | } 152 | Hole(_) => { 153 | self.write(&format!( 154 | "{}", 155 | Color::Red.bold().paint(format!("< - hole - >")) 156 | )); 157 | } 158 | Repeated => { 159 | self.write(&format!( 160 | "{}", 161 | Color::Green.paint(format!("[#{}]", repeated_idx.unwrap_or(0))) 162 | )); 163 | } 164 | BorrowedMut => { 165 | self.write(&format!( 166 | "{}", 167 | Color::Red.bold().paint(format!("< borrowed-mut >")) 168 | )); 169 | } 170 | Locked => { 171 | self.write(&format!( 172 | "{}", 173 | Color::Red.bold().paint(format!("< locked >")) 174 | )); 175 | } 176 | Leaf(s) => { 177 | self.write(s); 178 | } 179 | }; 180 | } 181 | 182 | fn inner_pretty_end(&mut self, elem: &NodeTree) { 183 | self.inner_pretty_print(elem); 184 | 185 | if self.line_used > 0 { 186 | self.end_line(); 187 | } 188 | } 189 | } 190 | 191 | pub fn pretty_format(elem: &NodeTree, settings: &NodePrinterSettings) { 192 | let mut state = Printer { 193 | settings, 194 | indent: 0, 195 | line_used: 0, 196 | item_linebreak: true, 197 | indent_string: String::from(""), 198 | seen: HashMap::new(), 199 | seen_idx: 1, 200 | }; 201 | 202 | state.inner_pretty_end(elem); 203 | } 204 | -------------------------------------------------------------------------------- /interact/src/access.rs: -------------------------------------------------------------------------------- 1 | /// This module defines the main traits used to dynamically operate and reflect on Rust types using Interact. 2 | use std::sync::Arc; 3 | 4 | use crate::deser::Deser; 5 | use crate::{deser, ClimbError, Climber, NodeTree, Reflector}; 6 | 7 | /// The indirect Reflect allows indirect climber or reflector access, and meant to be used as a 8 | /// trait object for that purpose. 9 | /// 10 | /// It is expected that the provided callback would be called at this or some other thread in order 11 | /// to continue traversal of the access expression. For example, if a processes uses internal message 12 | /// passing, the traversal can continue upon message reception. 13 | pub trait ReflectIndirect { 14 | /// Provides indirection for immutable access. 15 | fn indirect(&self, fnc: Box); 16 | 17 | /// Provides indirection for mutable access. 18 | fn indirect_mut(&mut self, fnc: Box); 19 | } 20 | 21 | /// The direct Reflect allows direct climber or reflector access, and meant 22 | /// to be used as a trait object for that purpose. 23 | pub trait ReflectDirect { 24 | /// The specific implementation of the following method will mostly likely call 25 | /// Reflector::reflect with the specific type. 26 | fn immut_reflector(&self, _reflector: &Arc) -> NodeTree; 27 | 28 | /// Implement climbing for the specific type. Returns a reflection of the inner value, 29 | /// depending on the expression remaining to parse. 30 | fn immut_climber<'a>(&self, _climber: &mut Climber<'a>) 31 | -> Result, ClimbError>; 32 | 33 | /// Implement mutable climbing for the specific type, allowing to modifying it. 34 | /// Returns a reflection of the inner value, depending on the expression remaining to parse. 35 | fn mut_climber<'a>( 36 | &mut self, 37 | _climber: &mut Climber<'a>, 38 | ) -> Result, ClimbError>; 39 | } 40 | 41 | /// An arbitrar between the two possible way to climb into an immutable value. 42 | pub enum Reflect<'a> { 43 | Indirect(&'a dyn ReflectIndirect), 44 | Direct(&'a dyn ReflectDirect), 45 | } 46 | 47 | pub struct Function { 48 | pub name: &'static str, 49 | pub args: &'static [&'static str], 50 | } 51 | 52 | /// MutAccess adds function call information over `ReflectMut`. 53 | pub struct MutAccess<'a> { 54 | pub reflect: ReflectMut<'a>, 55 | pub functions: &'static [Function], 56 | } 57 | 58 | impl<'a> MutAccess<'a> { 59 | pub fn no_funcs(reflect: ReflectMut<'a>) -> Self { 60 | Self { 61 | reflect, 62 | functions: &[], 63 | } 64 | } 65 | } 66 | 67 | /// ImmutAccess adds function call information over `Reflect`. 68 | pub struct ImmutAccess<'a> { 69 | pub reflect: Reflect<'a>, 70 | pub functions: &'static [Function], 71 | } 72 | 73 | impl<'a> ImmutAccess<'a> { 74 | pub fn no_funcs(reflect: Reflect<'a>) -> Self { 75 | Self { 76 | reflect, 77 | functions: &[], 78 | } 79 | } 80 | } 81 | 82 | /// An arbitrar between the two possible way to climb into a mutable value. 83 | pub enum ReflectMut<'a> { 84 | Indirect(&'a mut dyn ReflectIndirect), 85 | Direct(&'a mut dyn ReflectDirect), 86 | 87 | /// Internally signals that the value is not really mutable, for example 88 | /// we cannot change a reference value field from Interact context. 89 | Immutable, 90 | } 91 | 92 | #[derive(Debug, Eq, PartialEq)] 93 | pub enum AssignError { 94 | Deser(deser::DeserError), 95 | 96 | /// Some types, having ignored fields, will be unbuildable. 97 | Unbuildable, 98 | 99 | /// Other values are immutable, such as reference values. 100 | Immutable, 101 | } 102 | 103 | #[derive(Debug, Eq, PartialEq)] 104 | pub enum CallError { 105 | Deser(deser::DeserError), 106 | 107 | /// Signals the Climber stack to retract into a mutable path so that the 108 | /// field we are attempting to operate on will be recalled in a mutable 109 | /// state. 110 | NeedMutable, 111 | 112 | /// The called function does not exist. 113 | NoSuchFunction, 114 | } 115 | 116 | pub type RetValCallback<'a> = Box)>; 117 | 118 | /// The `Access` trait, meant to be used as a trait object, provides methods that 119 | /// dynamically expose read&write access to the underlying objects. 120 | pub trait Access { 121 | /// Expose an immmutable accessor, used when `Access` is immutable or mutable. 122 | fn immut_access(&self) -> ImmutAccess; 123 | 124 | /// Expose a mutable accessor, used when `Access` is mutable. 125 | fn mut_access(&mut self) -> MutAccess; 126 | 127 | /// Perform an optional method call for a certain function, with the return value provided to 128 | /// the callback. The arguments are parsed from the Token tracker in the Climber parameter. 129 | /// 130 | /// Depending on the state of the Climber, we may just parsing the arguments not not actually 131 | /// calling the function, in order to provide user feedback. 132 | fn immut_call<'a>( 133 | &self, 134 | _func_name: &'static str, 135 | _climber: &mut Climber<'a>, 136 | mut _retcall: RetValCallback<'a>, 137 | ) -> Result<(), CallError> { 138 | Err(CallError::NoSuchFunction) 139 | } 140 | 141 | /// Perform an optional method call for a certain function which may modify the underlying 142 | /// value, with the return value provided to the callback. The arguments are parsed from the 143 | /// Token tracker in the Climber parameter. 144 | /// 145 | /// Depending on the state of the Climber, we may just parsing the arguments not not actually 146 | /// calling the function, in order to provide user feedback. 147 | fn mut_call<'a>( 148 | &mut self, 149 | _func_name: &'static str, 150 | _climber: &mut Climber<'a>, 151 | mut _retcall: RetValCallback<'a>, 152 | ) -> Result<(), CallError> { 153 | Err(CallError::NoSuchFunction) 154 | } 155 | 156 | /// Assign a new value to this object. `probe_only` determines whether the implementation would 157 | /// only parse the new value and not actually assign it. This is in order to provide user 158 | /// feedback for the parsing bits. 159 | fn mut_assign<'a, 'b>( 160 | &mut self, 161 | _tokens: &mut deser::Tracker<'a, 'b>, 162 | _probe_only: bool, 163 | ) -> Result<(), AssignError> { 164 | Err(AssignError::Unbuildable) 165 | } 166 | } 167 | 168 | macro_rules! mut_assign_deser { 169 | () => { 170 | fn mut_assign<'x, 'y>( 171 | &mut self, 172 | tracker: &mut deser::Tracker<'x, 'y>, 173 | probe_only: bool, 174 | ) -> Result<(), AssignError> { 175 | crate::access::deser_assign(self, tracker, probe_only) 176 | } 177 | } 178 | } 179 | 180 | /// A helper for the specific implementations of `Access` to use with `mut_assign` methods 181 | pub fn deser_assign<'a, 'b, T: Deser>( 182 | dest: &mut T, 183 | tracker: &mut deser::Tracker<'a, 'b>, 184 | probe_only: bool, 185 | ) -> Result<(), AssignError> { 186 | match T::deser(tracker) { 187 | Ok(v) => { 188 | if !probe_only { 189 | *dest = v; 190 | } 191 | Ok(()) 192 | } 193 | Err(e) => Err(AssignError::Deser(e)), 194 | } 195 | } 196 | 197 | mod basic; 198 | mod btreemap; 199 | mod derefs; 200 | pub mod derive; 201 | mod explicit; 202 | mod hashmap; 203 | mod hashset; 204 | mod instant; 205 | pub mod iter; 206 | mod mutex; 207 | mod refcell; 208 | mod tuple; 209 | pub mod vec; 210 | -------------------------------------------------------------------------------- /interact/src/tokens/mod.rs: -------------------------------------------------------------------------------- 1 | /// Token parser for Interact input. 2 | /// 3 | /// Behind the scenes, we would like to perform parsing of certain types of expressions, mainly for 4 | /// the purpose of data access. The most common type of data access is dereferences of a struct 5 | /// field, i.e., `.field`. Others, include map indexing `["value"]`, so we only need a certain 6 | /// subset of Rust tokens. 7 | /// 8 | /// However, to make implementation easier, we only do basic splitting of tokens here using `pest`, 9 | /// and for the basic types, allow `ron` to do a more in depth resolve of the basic Rust types 10 | /// into the actual values. 11 | 12 | #[derive(Parser)] 13 | #[grammar = "tokens/parse.pest"] 14 | pub struct ExprParser; 15 | use std::borrow::Cow; 16 | 17 | use pest::Parser; 18 | 19 | #[derive(Debug, Clone, Eq, PartialEq)] 20 | pub enum TokenKind { 21 | Ident, 22 | NonNegativeDecimal(u64), 23 | Decimal(i64), 24 | SubscriptOpen, 25 | SubscriptClose, 26 | TupleOpen, 27 | TupleClose, 28 | CurlyOpen, 29 | CurlyClose, 30 | FieldAccess, 31 | Assign, 32 | Colon, 33 | Asterix, 34 | Char(char), 35 | String(String), 36 | Range(bool), 37 | Comma, 38 | InvalidToken, 39 | } 40 | 41 | /// Represents a single meaningful substring part in an Interact string expression. 42 | #[derive(Debug, Clone, Eq, PartialEq)] 43 | pub struct Token<'a> { 44 | /// Token kind 45 | pub kind: TokenKind, 46 | 47 | /// Token text 48 | pub text: Cow<'a, str>, 49 | 50 | /// Amount of whitespace from the previous token 51 | pub space_diff: usize, 52 | } 53 | 54 | impl<'a> Token<'a> { 55 | pub fn new_owned(s: String) -> Self { 56 | Token { 57 | kind: TokenKind::Ident, 58 | text: Cow::Owned(s), 59 | space_diff: 0, 60 | } 61 | } 62 | 63 | pub fn new_borrowed(kind: TokenKind, s: &'static str) -> Self { 64 | Token { 65 | kind, 66 | text: Cow::from(s), 67 | space_diff: 0, 68 | } 69 | } 70 | 71 | pub fn clone_owned(&self) -> Token<'static> { 72 | Token { 73 | kind: self.kind.clone(), 74 | text: Cow::Owned(String::from(self.text.as_ref())), 75 | space_diff: self.space_diff, 76 | } 77 | } 78 | 79 | /// Returns whether two tokens are idential with whitespace removed. 80 | pub fn similar(&self, token: &Token) -> bool { 81 | if self.kind != token.kind { 82 | return false; 83 | } 84 | 85 | let mut a = self.text.split_whitespace(); 86 | let mut b = token.text.split_whitespace(); 87 | a.next() == b.next() 88 | } 89 | 90 | /// Returns whether one token is a prefix or another. 91 | pub fn is_prefix_of(&self, token: &Token) -> bool { 92 | if self.kind != token.kind { 93 | return false; 94 | } 95 | 96 | token.text.starts_with(self.text.as_ref()) 97 | } 98 | 99 | /// Return the amount of space following the text of the token. 100 | pub fn space_suffix(&self) -> usize { 101 | let mut a = self.text.split_whitespace(); 102 | self.text.len() - a.next().unwrap().len() 103 | } 104 | } 105 | 106 | /// Wrapper for the traversal of a borrowed list of tokens. 107 | #[derive(Debug, Clone)] 108 | pub struct TokenVec<'a> { 109 | tokens: Cow<'a, [Token<'a>]>, 110 | pos: usize, 111 | } 112 | 113 | impl<'a> TokenVec<'a> { 114 | pub fn new(tokens: &'a [Token<'a>]) -> Self { 115 | Self { 116 | tokens: Cow::Borrowed(tokens), 117 | pos: 0, 118 | } 119 | } 120 | 121 | pub fn clone_owned(&self) -> TokenVec<'static> { 122 | let mut v = vec![]; 123 | 124 | for token in self.tokens.iter() { 125 | v.push(token.clone_owned()); 126 | } 127 | 128 | TokenVec { 129 | tokens: Cow::Owned(v), 130 | pos: self.pos, 131 | } 132 | } 133 | 134 | pub fn take_pos(&mut self, other: usize) { 135 | self.pos = other; 136 | } 137 | 138 | pub fn pos(&self) -> usize { 139 | self.pos 140 | } 141 | 142 | pub fn new_empty() -> Self { 143 | Self { 144 | tokens: Cow::Borrowed(&[]), 145 | pos: 0, 146 | } 147 | } 148 | 149 | pub fn remaining(&self) -> usize { 150 | self.tokens.len() - self.pos 151 | } 152 | 153 | pub fn has_remaining(&self) -> bool { 154 | self.remaining() > 0 155 | } 156 | 157 | pub fn is_empty(&self) -> bool { 158 | self.remaining() == 0 159 | } 160 | 161 | pub fn top(&self) -> &Token<'a> { 162 | &self.tokens[self.pos] 163 | } 164 | 165 | pub fn top_kind(&self) -> &TokenKind { 166 | &self.tokens[self.pos].kind 167 | } 168 | 169 | pub fn len(&self) -> usize { 170 | self.tokens.len() 171 | } 172 | 173 | pub fn advance(&mut self, count: usize) { 174 | self.pos += count; 175 | if self.pos > self.tokens.len() { 176 | panic!("invalid token advance {} > {}", self.pos, self.tokens.len()); 177 | } 178 | } 179 | 180 | pub fn step(&mut self) { 181 | self.advance(1) 182 | } 183 | } 184 | 185 | #[derive(Debug)] 186 | pub enum Error { 187 | Pest(pest::error::Error), 188 | IntError(std::num::ParseIntError), 189 | RonError(ron::de::Error), 190 | } 191 | 192 | /// Parse a string into a vector of tokens. 193 | pub fn parse_to_tokens<'a>(s: &'a str) -> Result>, Error> { 194 | let mut vec = vec![]; 195 | 196 | let pairs = ExprParser::parse(Rule::token_list, s).map_err(Error::Pest)?; 197 | let mut last_end = 0; 198 | 199 | for pair in pairs { 200 | let span = pair.clone().as_span(); 201 | let mut stop = false; 202 | 203 | let token_inner = match pair.as_rule() { 204 | Rule::identifier => TokenKind::Ident, 205 | Rule::nonnegative_decimal => { 206 | TokenKind::NonNegativeDecimal(span.as_str().parse().map_err(Error::IntError)?) 207 | } 208 | Rule::decimal => { 209 | TokenKind::Decimal(ron::de::from_str(span.as_str()).map_err(Error::RonError)?) 210 | } 211 | Rule::invalid => { 212 | stop = true; 213 | TokenKind::InvalidToken 214 | } 215 | Rule::field_access => TokenKind::FieldAccess, 216 | Rule::subscript_open => TokenKind::SubscriptOpen, 217 | Rule::subscript_close => TokenKind::SubscriptClose, 218 | Rule::tuple_open => TokenKind::TupleOpen, 219 | Rule::tuple_close => TokenKind::TupleClose, 220 | Rule::curly_open => TokenKind::CurlyOpen, 221 | Rule::curly_close => TokenKind::CurlyClose, 222 | Rule::comma => TokenKind::Comma, 223 | Rule::colon => TokenKind::Colon, 224 | Rule::asterix => TokenKind::Asterix, 225 | Rule::char_literal => { 226 | TokenKind::Char(ron::de::from_str(span.as_str()).map_err(Error::RonError)?) 227 | } 228 | Rule::assign => TokenKind::Assign, 229 | Rule::range_access => TokenKind::Range(false), 230 | Rule::range_access_inclusive => TokenKind::Range(true), 231 | Rule::string_literal => { 232 | TokenKind::String(ron::de::from_str(span.as_str()).map_err(Error::RonError)?) 233 | } 234 | Rule::underscore 235 | | Rule::alpha 236 | | Rule::alphanumeric 237 | | Rule::digit 238 | | Rule::nonzero 239 | | Rule::token 240 | | Rule::negative_decimal 241 | | Rule::escape_sequence 242 | | Rule::whitespace_char 243 | | Rule::literal_char 244 | | Rule::single_literal_char 245 | | Rule::token_list 246 | | Rule::WHITESPACE => { 247 | continue; 248 | } 249 | }; 250 | 251 | vec.push(Token { 252 | kind: token_inner, 253 | text: Cow::Borrowed(span.as_str()), 254 | space_diff: span.start() - last_end, 255 | }); 256 | 257 | if stop { 258 | break; 259 | } 260 | 261 | last_end = span.end(); 262 | } 263 | 264 | Ok(vec) 265 | } 266 | -------------------------------------------------------------------------------- /interact/tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | /// Here we define types and data to be used in tests and examples. 4 | pub mod pseudo_mutex; 5 | mod random; 6 | use pseudo_mutex::PseudoMutex; 7 | pub use random::Rand; 8 | 9 | use std::cell::RefCell; 10 | use std::collections::{BTreeMap, HashMap}; 11 | use std::iter::FromIterator; 12 | use std::rc::Rc; 13 | use std::sync::{Arc, Mutex}; 14 | use std::time::Instant; 15 | 16 | use interact::Interact; 17 | use rand::Rng; 18 | 19 | #[derive(Interact)] 20 | pub struct Basic { 21 | u_s: usize, 22 | is: isize, 23 | u_64: u64, 24 | u_32: u32, 25 | u_16: u16, 26 | u_8: u8, 27 | bo: bool, 28 | st: String, 29 | ch: char, 30 | i_64: i64, 31 | i_32: i32, 32 | i_16: i16, 33 | i_8: u8, 34 | arr: [u8; 4], 35 | option_none: Option, 36 | option_some: Option, 37 | result_ok: Result, 38 | result_err: Result, 39 | } 40 | 41 | fn new_string_random(rng: &mut R) -> String { 42 | const CHARSET: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\ 43 | abcdefghijklmnopqrstuvwxyz\ 44 | 0123456789_"; 45 | String::from_iter((0..20).map(|_| { 46 | CHARSET 47 | .chars() 48 | .nth(rng.gen::() % CHARSET.len()) 49 | .unwrap() 50 | })) 51 | } 52 | 53 | impl Rand for Basic { 54 | fn new_random(rng: &mut R) -> Self { 55 | Self { 56 | u_s: rng.gen(), 57 | is: rng.gen(), 58 | u_64: rng.gen(), 59 | u_32: rng.gen(), 60 | u_16: rng.gen(), 61 | u_8: rng.gen(), 62 | arr: [rng.gen(), rng.gen(), rng.gen(), rng.gen()], 63 | bo: rng.gen(), 64 | st: new_string_random(rng), 65 | ch: rng.gen(), 66 | i_64: rng.gen(), 67 | i_32: rng.gen(), 68 | i_16: rng.gen(), 69 | i_8: rng.gen(), 70 | option_none: None, 71 | option_some: Some(rng.gen()), 72 | result_ok: Ok(rng.gen()), 73 | result_err: Err(rng.gen()), 74 | } 75 | } 76 | } 77 | 78 | #[derive(Interact, Hash, Eq, PartialEq)] 79 | pub struct Key { 80 | field_a: u64, 81 | field_b: u32, 82 | } 83 | 84 | impl Rand for Key { 85 | fn new_random(rng: &mut R) -> Self { 86 | Self { 87 | field_a: rng.gen(), 88 | field_b: rng.gen(), 89 | } 90 | } 91 | } 92 | 93 | #[derive(Interact)] 94 | pub enum EnumExample { 95 | VarUnit, 96 | VarUnnamed(u8, u32), 97 | VarNamed { a: u8, b: u16 }, 98 | } 99 | 100 | #[derive(Interact)] 101 | pub struct RefsAndLocks { 102 | arc_a: Arc, 103 | arc_b: Arc, 104 | arc_c: Arc, 105 | arc_d: Arc, 106 | arc_e: Arc, 107 | arc_f: Arc, 108 | } 109 | 110 | impl Rand for RefsAndLocks { 111 | fn new_random(rng: &mut R) -> Self { 112 | let arc_a: Arc = Arc::new(Rand::new_random(rng)); 113 | let arc_b = Arc::new(Key { 114 | field_a: Rand::new_random(rng), 115 | field_b: Rand::new_random(rng), 116 | }); 117 | let arc_c = arc_b.clone(); 118 | let arc_d = arc_a.clone(); 119 | let arc_e = Arc::new(Rand::new_random(rng)); 120 | let arc_f = Arc::new(Rand::new_random(rng)); 121 | 122 | Self { 123 | arc_a, 124 | arc_b, 125 | arc_c, 126 | arc_d, 127 | arc_e, 128 | arc_f, 129 | } 130 | } 131 | } 132 | 133 | #[derive(Interact)] 134 | struct UnnamedFields(String, u32); 135 | 136 | #[derive(Interact)] 137 | struct UnitStruct; 138 | 139 | /// A doc comment for testing. 140 | #[derive(Interact)] 141 | #[interact(immut_fn(check()))] 142 | #[interact(mut_fn(add(a)))] 143 | pub struct Complex { 144 | simple: HashMap, 145 | complex_key: HashMap, 146 | map: BTreeMap, 147 | struct_unnamed: UnnamedFields, 148 | struct_unit: UnitStruct, 149 | enum_unit: EnumExample, 150 | enum_unnamed: EnumExample, 151 | enum_named: EnumExample, 152 | boxed: Box, 153 | tuple: ((u32, EnumExample, (u8, u8)), i32), 154 | tuple_1: (u32,), 155 | vec: Vec<(u32, EnumExample)>, 156 | behind_mutex: Mutex, 157 | behind_pseudo_mutex: PseudoMutex, 158 | behind_arc_mutex: Arc>, 159 | instant: Instant, 160 | refs: RefsAndLocks, 161 | } 162 | 163 | impl Complex { 164 | fn check(&self) -> bool { 165 | self.tuple.0 .0 == self.tuple_1.0 166 | } 167 | 168 | fn add(&mut self, a: u32) { 169 | self.vec[0].0 += a; 170 | } 171 | } 172 | 173 | impl Rand for Complex { 174 | fn new_random(rng: &mut R) -> Self { 175 | let mut simple = HashMap::new(); 176 | let mut complex_key = HashMap::new(); 177 | let mut map = BTreeMap::new(); 178 | 179 | for _ in 0..(2 + rng.gen::() / 32) { 180 | complex_key.insert(Rand::new_random(rng), Rand::new_random(rng)); 181 | } 182 | for _ in 0..(2 + rng.gen::() / 32) { 183 | simple.insert(Rand::new_random(rng), Rand::new_random(rng)); 184 | } 185 | for _ in 0..(2 + rng.gen::() / 32) { 186 | map.insert(new_string_random(rng), Rand::new_random(rng)); 187 | } 188 | 189 | Self { 190 | simple, 191 | complex_key, 192 | map, 193 | struct_unit: UnitStruct, 194 | struct_unnamed: UnnamedFields(new_string_random(rng), Rand::new_random(rng)), 195 | enum_unit: EnumExample::VarUnit, 196 | enum_unnamed: EnumExample::VarUnnamed(Rand::new_random(rng), Rand::new_random(rng)), 197 | enum_named: EnumExample::VarNamed { 198 | a: Rand::new_random(rng), 199 | b: Rand::new_random(rng), 200 | }, 201 | boxed: Box::new(EnumExample::VarUnit), 202 | tuple: ( 203 | ( 204 | Rand::new_random(rng), 205 | EnumExample::VarUnit, 206 | (Rand::new_random(rng), Rand::new_random(rng)), 207 | ), 208 | Rand::new_random(rng), 209 | ), 210 | tuple_1: (0,), 211 | refs: Rand::new_random(rng), 212 | behind_arc_mutex: Arc::new(Mutex::new(Rand::new_random(rng))), 213 | behind_mutex: Mutex::new(Rand::new_random(rng)), 214 | behind_pseudo_mutex: PseudoMutex::new(Rand::new_random(rng)), 215 | vec: vec![ 216 | (Rand::new_random(rng), EnumExample::VarUnit), 217 | (Rand::new_random(rng), EnumExample::VarNamed { a: 3, b: 4 }), 218 | ], 219 | instant: Instant::now(), 220 | } 221 | } 222 | } 223 | 224 | #[derive(Interact)] 225 | pub struct Chain { 226 | value: u32, 227 | nest: Option>>, 228 | } 229 | 230 | #[derive(Interact)] 231 | pub struct LocalRcLoop { 232 | chain: Chain, 233 | loop_chain: Chain, 234 | } 235 | 236 | impl Rand for LocalRcLoop { 237 | fn new_random(rng: &mut R) -> Self { 238 | let node = Rc::new(RefCell::new(Chain { 239 | value: Rand::new_random(rng), 240 | nest: Some(Rc::new(RefCell::new(Chain { 241 | value: Rand::new_random(rng), 242 | nest: None, 243 | }))), 244 | })); 245 | 246 | node.borrow_mut().nest.as_ref().unwrap().borrow_mut().nest = Some(node.clone()); 247 | 248 | Self { 249 | chain: Chain { 250 | value: Rand::new_random(rng), 251 | nest: Some(Rc::new(RefCell::new(Chain { 252 | value: Rand::new_random(rng), 253 | nest: Some(Rc::new(RefCell::new(Chain { 254 | value: Rand::new_random(rng), 255 | nest: None, 256 | }))), 257 | }))), 258 | }, 259 | loop_chain: Chain { 260 | value: Rand::new_random(rng), 261 | nest: Some(node), 262 | }, 263 | } 264 | } 265 | } 266 | 267 | /// A doc comment for testing. 268 | #[derive(Interact)] 269 | pub struct LocalComplex { 270 | rc_loop: LocalRcLoop, 271 | } 272 | -------------------------------------------------------------------------------- /interact/src/root.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use crate::{ 4 | tokens::parse_to_tokens, Access, Assist, ClimbError, Climber, NextOptions, NodeTree, Token, 5 | }; 6 | 7 | /// Holds a root dictionary of `Send`-able trait objects that implement `Access` and are therefore 8 | /// Interact-able. These are most likely objects that are held globally behind an `Arc`. 9 | #[derive(Default)] 10 | pub struct RootSend { 11 | pub owned: BTreeMap<&'static str, Box>, 12 | } 13 | 14 | impl RootSend { 15 | pub fn new() -> Self { 16 | Default::default() 17 | } 18 | 19 | pub fn as_root(&mut self) -> Root { 20 | Root { 21 | send: Some(self), 22 | local: None, 23 | } 24 | } 25 | } 26 | 27 | /// Holds a root dictionary of trait objects that implement `Access` and are therefore Interact-able. 28 | /// These are most likely objects that are held locally behind an `Rc`. 29 | #[derive(Default)] 30 | pub struct RootLocal { 31 | pub owned: BTreeMap<&'static str, Box>, 32 | } 33 | 34 | impl RootLocal { 35 | pub fn new() -> Self { 36 | Default::default() 37 | } 38 | } 39 | 40 | /// A temporary binder of `RootSend` and `RootLocal` dictionaries, used for providing a unified 41 | /// dictionary to the user. 42 | pub struct Root<'a, 'b> { 43 | pub send: Option<&'a mut RootSend>, 44 | pub local: Option<&'b mut RootLocal>, 45 | } 46 | 47 | impl<'a, 'b> Root<'a, 'b> { 48 | /// Probe a path, checking if it is valid. If it contains a function name, it will not be 49 | /// called. If it contains an assignment, the assignment will not take place but the parameters 50 | /// value will check for `Deser` deserialization. 51 | /// 52 | /// This call may block the current thread until `ReflectIndirect` evaluation is resolved, and 53 | /// it may block because some fields, depending on the types and usage, could be behind a 54 | /// `Mutex` lock. 55 | pub fn probe(&mut self, path_str: &str) -> (Result, Assist) { 56 | self._access(path_str, true) 57 | } 58 | 59 | /// Perform evaluation of the provided path. This may perform assignments, or call user-defined 60 | /// functions via the `#[interact(...)]` type attribute. 61 | /// 62 | /// This call may block the current thread until `ReflectIndirect` evaluation is resolved, and 63 | /// it may block because some fields, depending on the types and usage, could be behind a 64 | /// `Mutex` lock. 65 | pub fn access(&mut self, path_str: &str) -> (Result, Assist) { 66 | self._access(path_str, false) 67 | } 68 | 69 | pub fn keys(&self) -> Vec<&'static str> { 70 | let mut v = vec![]; 71 | match &self.send { 72 | None => {} 73 | Some(x) => { 74 | for k in x.owned.keys() { 75 | v.push(*k); 76 | } 77 | } 78 | } 79 | match &self.local { 80 | None => {} 81 | Some(x) => { 82 | for k in x.owned.keys() { 83 | v.push(*k); 84 | } 85 | } 86 | } 87 | 88 | v 89 | } 90 | 91 | fn _access( 92 | &mut self, 93 | path_str: &str, 94 | probe_only: bool, 95 | ) -> (Result, Assist) { 96 | enum Item<'a, 'b> { 97 | Send(&'a mut Box), 98 | Local(&'b mut Box), 99 | }; 100 | let mut h = std::collections::BTreeMap::new(); 101 | match &mut self.send { 102 | None => {} 103 | Some(x) => { 104 | for (k, v) in x.owned.iter_mut() { 105 | h.insert(*k, Item::Send(v)); 106 | } 107 | } 108 | } 109 | match &mut self.local { 110 | None => {} 111 | Some(x) => { 112 | for (k, v) in x.owned.iter_mut() { 113 | h.insert(*k, Item::Local(v)); 114 | } 115 | } 116 | } 117 | let matching_prefix_keys = h 118 | .keys() 119 | .filter(|x| x.starts_with(path_str)) 120 | .map(|x| String::from(&x[..])) 121 | .collect(); 122 | 123 | let ret_assist = |valid| { 124 | let mut assist = Assist::default(); 125 | let matching: Vec<_> = matching_prefix_keys; 126 | if !matching.is_empty() { 127 | assist.pend(valid) 128 | } 129 | assist.next_options(NextOptions::Avail(0, matching)) 130 | }; 131 | let tokens = match parse_to_tokens(path_str).map_err(ClimbError::TokenError) { 132 | Err(err) => { 133 | return (Err(err), ret_assist(0)); 134 | } 135 | Ok(tokens) => tokens, 136 | }; 137 | if tokens.is_empty() { 138 | return (Err(ClimbError::NullPath), ret_assist(0)); 139 | } 140 | 141 | let first_token = tokens[0].text.as_ref(); 142 | let item = match h.get(first_token) { 143 | Some(v) => v, 144 | None => { 145 | return ( 146 | Err(ClimbError::MissingStartComponent), 147 | ret_assist(first_token.len()), 148 | ); 149 | } 150 | }; 151 | 152 | let start_pos = tokens[0].space_diff + tokens[0].text.len(); 153 | let tokens = &tokens[1..]; 154 | let mut climber = Climber::new(200, probe_only, tokens); 155 | let climber_clone = climber.clone(); 156 | 157 | let mut res = match item { 158 | Item::Local(x) => match climber.general_access_immut(&***x) { 159 | Err(ClimbError::NeedMutPath) => match h.get_mut(first_token) { 160 | Some(Item::Local(item)) => { 161 | climber = climber_clone; 162 | climber.general_access_mut(&mut ***item) 163 | } 164 | _ => { 165 | return ( 166 | Err(ClimbError::MissingStartComponent), 167 | ret_assist(first_token.len()), 168 | ); 169 | } 170 | }, 171 | e => e, 172 | }, 173 | Item::Send(x) => match climber.general_access_immut(&***x) { 174 | Err(ClimbError::NeedMutPath) => match h.get_mut(first_token) { 175 | Some(Item::Send(item)) => { 176 | climber = climber_clone; 177 | climber.general_access_mut(&mut ***item) 178 | } 179 | _ => { 180 | return ( 181 | Err(ClimbError::MissingStartComponent), 182 | ret_assist(first_token.len()), 183 | ); 184 | } 185 | }, 186 | e => e, 187 | }, 188 | }; 189 | 190 | match &mut res { 191 | Ok(res) => { 192 | res.resolve(); 193 | } 194 | Err(_) => {} 195 | } 196 | 197 | let (old_assist, pending_partial) = climber.convert_to_assist(); 198 | 199 | // Convert the tokens-based Assist back to String-based assist 200 | let mut new_assist = Assist::default(); 201 | 202 | let (valid, pending, pending_special, next_options) = old_assist.dismantle(); 203 | 204 | let mut valid_len = start_pos; 205 | for token in tokens.iter().take(valid) { 206 | valid_len += token.space_diff; 207 | valid_len += token.text.len(); 208 | } 209 | new_assist.pend(valid_len); 210 | new_assist.commit_pending(); 211 | 212 | let mut pending_len = 0; 213 | let mut pending_avail = 0; 214 | let mut pending_special_len = 0; 215 | for (i, token) in tokens.iter().enumerate().skip(valid).take(pending) { 216 | let nr_pending = i - valid; 217 | pending_len += token.space_diff; 218 | pending_len += token.text.len(); 219 | if let NextOptions::Avail(pos, _) = next_options { 220 | if nr_pending < pos { 221 | pending_avail = pending_len; 222 | } 223 | } 224 | if i >= valid + pending - pending_special { 225 | pending_special_len += token.space_diff; 226 | pending_special_len += token.text.len(); 227 | } 228 | } 229 | new_assist.pend(pending_len); 230 | new_assist.set_pending_special(pending_special_len); 231 | 232 | let conv = |v: Vec>>| { 233 | let mut str_suggestions = vec![]; 234 | for suggestion in v { 235 | let mut s = String::new(); 236 | for token in suggestion { 237 | let spaces = " "; 238 | let max_space = std::cmp::min(spaces.len(), token.space_diff); 239 | s.push_str(&spaces[..max_space]); 240 | s.push_str(token.text.as_ref()); 241 | } 242 | str_suggestions.push(s); 243 | } 244 | str_suggestions 245 | }; 246 | 247 | let next_options = match next_options { 248 | NextOptions::NoOptions => NextOptions::NoOptions, 249 | NextOptions::Avail(_, v) => { 250 | NextOptions::Avail(pending_avail - pending_partial, conv(v)) 251 | } 252 | }; 253 | 254 | (res, new_assist.next_options(next_options)) 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /interact/src/reflector.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::Entry; 2 | use std::collections::HashMap; 3 | use std::sync::atomic::{AtomicUsize, Ordering}; 4 | use std::sync::mpsc::channel; 5 | use std::sync::{Arc, Mutex}; 6 | use std::thread::ThreadId; 7 | 8 | use crate::access::vec::ReflectVec; 9 | use crate::access::{ 10 | derive::{ReflectStruct, Struct, StructKind}, 11 | iter::ReflectIter, 12 | Access, 13 | }; 14 | use crate::node_tree::{NodeInfo, NodeTree, PtrMeta, Wrap}; 15 | 16 | type ObjPtr = (usize, usize); 17 | 18 | /// `Reflector` operates on types implementing `Access`. Some of its methods are being called 19 | /// automatically from `#[derive(Interact)]` impls. It provides a thread-safe context, because on 20 | /// the extreme case, where it is possible that reflection is done via indirection using multiple 21 | /// process threads (see `ReflectIndirect`). 22 | pub struct Reflector { 23 | limit: usize, 24 | used: AtomicUsize, 25 | 26 | seen: Mutex>, 27 | synced_thread: ThreadId, 28 | } 29 | 30 | impl Reflector { 31 | pub fn new(limit: usize) -> Arc { 32 | Arc::new(Self { 33 | limit, 34 | used: AtomicUsize::new(0), 35 | seen: Mutex::new(HashMap::new()), 36 | synced_thread: std::thread::current().id(), 37 | }) 38 | } 39 | 40 | pub fn reflect_struct( 41 | a_self: &Arc, 42 | desc: &Struct, 43 | p_struct: &dyn ReflectStruct, 44 | anon: bool, 45 | ) -> NodeTree { 46 | let meta = try_seen_dyn!(p_struct, a_self); 47 | 48 | match &desc.kind { 49 | StructKind::Unit => { 50 | NodeInfo::Leaf(std::borrow::Cow::Borrowed(desc.name)).with_meta(meta) 51 | } 52 | StructKind::Tuple(n) => { 53 | let mut v = vec![]; 54 | 55 | for i in 0..*n { 56 | if a_self.limit <= a_self.used.load(Ordering::Relaxed) { 57 | v.push(NodeInfo::Limited.into_node()); 58 | break; 59 | } 60 | 61 | let reflect_node = Self::reflect(a_self, p_struct.get_field_by_idx(i).unwrap()); 62 | v.push(reflect_node); 63 | } 64 | 65 | let grouped = 66 | NodeInfo::Grouped('(', Box::new(NodeInfo::Delimited(',', v).into_node()), ')'); 67 | 68 | let elem = if !desc.name.is_empty() && !anon { 69 | NodeInfo::named(desc.name, grouped.into_node()) 70 | } else { 71 | grouped 72 | }; 73 | 74 | elem.with_meta(meta) 75 | } 76 | StructKind::Fields(fields) => { 77 | let mut result = vec![]; 78 | let mut items = vec![]; 79 | let mut missing_keys = false; 80 | 81 | for field in *fields { 82 | if a_self.limit <= a_self.used.load(Ordering::Relaxed) { 83 | missing_keys = true; 84 | break; 85 | } 86 | 87 | a_self.used.fetch_add(1, Ordering::SeqCst); 88 | items.push((field, p_struct.get_field_by_name(field).unwrap())); 89 | } 90 | 91 | for (key, value) in items.into_iter() { 92 | let node = { 93 | if a_self.limit <= a_self.used.load(Ordering::Relaxed) { 94 | NodeInfo::Limited.into_node() 95 | } else { 96 | Self::reflect(a_self, value) 97 | } 98 | }; 99 | 100 | result.push( 101 | NodeInfo::Tuple( 102 | Box::new(NodeInfo::Leaf(std::borrow::Cow::Borrowed(key)).into_node()), 103 | ":", 104 | Box::new(node), 105 | ) 106 | .into_node(), 107 | ) 108 | } 109 | 110 | if missing_keys { 111 | result.push(NodeInfo::Limited.into_node()); 112 | } 113 | 114 | let grouped = NodeInfo::Grouped( 115 | '{', 116 | Box::new(NodeInfo::Delimited(',', result).into_node()), 117 | '}', 118 | ); 119 | 120 | let elem = if !desc.name.is_empty() && !anon { 121 | NodeInfo::named(desc.name, grouped.into_node()) 122 | } else { 123 | grouped 124 | }; 125 | 126 | elem.with_meta(meta) 127 | } 128 | } 129 | } 130 | 131 | pub fn reflect_map( 132 | a_self: &Arc, 133 | iter: &mut dyn ReflectIter<(&dyn Access, &dyn Access)>, 134 | name: &'static str, 135 | ) -> NodeTree { 136 | let meta = try_seen_dyn!(iter, a_self); 137 | 138 | let mut result = vec![]; 139 | let mut items = vec![]; 140 | let mut missing_keys = false; 141 | 142 | while let Some((key, value)) = iter.reflect_next() { 143 | if a_self.limit <= a_self.used.load(Ordering::Relaxed) { 144 | missing_keys = true; 145 | break; 146 | } 147 | 148 | a_self.used.fetch_add(1, Ordering::SeqCst); 149 | items.push((Self::reflect(a_self, key), value)); 150 | } 151 | 152 | for (key, value) in items.into_iter() { 153 | let node = { 154 | if a_self.limit <= a_self.used.load(Ordering::Relaxed) { 155 | NodeInfo::Limited.into_node() 156 | } else { 157 | Self::reflect(a_self, value) 158 | } 159 | }; 160 | 161 | let reflect_node = NodeInfo::Tuple(Box::new(key), ":", Box::new(node)); 162 | result.push(reflect_node.into_node()); 163 | } 164 | 165 | if missing_keys { 166 | result.push(NodeInfo::Limited.into_node()); 167 | } 168 | 169 | NodeInfo::named( 170 | name, 171 | NodeInfo::Grouped( 172 | '{', 173 | Box::new(NodeInfo::Delimited(',', result).into_node()), 174 | '}', 175 | ) 176 | .into_node(), 177 | ) 178 | .with_meta(meta) 179 | } 180 | 181 | pub fn reflect_set( 182 | a_self: &Arc, 183 | iter: &mut dyn ReflectIter<&dyn Access>, 184 | name: &'static str, 185 | ) -> NodeTree { 186 | let mut v = vec![]; 187 | let meta = try_seen_dyn!(iter, a_self); 188 | 189 | while let Some(member) = iter.reflect_next() { 190 | if a_self.limit <= a_self.used.load(Ordering::Relaxed) { 191 | v.push(NodeInfo::Limited.into_node()); 192 | break; 193 | } 194 | 195 | let member = Self::reflect(a_self, member); 196 | v.push(member); 197 | } 198 | 199 | NodeInfo::named( 200 | name, 201 | NodeInfo::Grouped('{', Box::new(NodeInfo::Delimited(',', v).into_node()), '}') 202 | .into_node(), 203 | ) 204 | .with_meta(meta) 205 | } 206 | 207 | pub fn reflect_vec(a_self: &Arc, vec: &dyn ReflectVec, name: &'static str) -> NodeTree { 208 | let mut v = vec![]; 209 | 210 | let meta = try_seen_dyn!(vec, a_self); 211 | 212 | for i in 0..vec.get_len() { 213 | if a_self.limit <= a_self.used.load(Ordering::Relaxed) { 214 | v.push(NodeInfo::Limited.into_node()); 215 | break; 216 | } 217 | 218 | let reflect_node = Self::reflect(a_self, vec.get_item(i).unwrap()); 219 | v.push(reflect_node); 220 | } 221 | 222 | let item = NodeInfo::Grouped('[', Box::new(NodeInfo::Delimited(',', v).into_node()), ']'); 223 | 224 | let item = if !name.is_empty() { 225 | NodeInfo::named(name, item.into_node()) 226 | } else { 227 | item 228 | }; 229 | 230 | item.with_meta(meta) 231 | } 232 | 233 | pub fn seen_ptr(a_self: &Arc, obj_ptr: ObjPtr) -> Result { 234 | let mut seen = a_self.seen.lock().unwrap(); 235 | match seen.entry(obj_ptr) { 236 | Entry::Occupied(entry) => { 237 | let entry = entry.get(); 238 | entry.fetch_add(1, Ordering::SeqCst); 239 | 240 | Ok(NodeTree::new(NodeInfo::Repeated, Some(Wrap(entry.clone())))) 241 | } 242 | Entry::Vacant(entry) => { 243 | let meta = Arc::new(AtomicUsize::new(1)); 244 | entry.insert(meta.clone()); 245 | Err(meta) 246 | } 247 | } 248 | } 249 | 250 | pub fn reflect(a_self: &Arc, access: &dyn Access) -> NodeTree { 251 | use crate::Reflect::*; 252 | 253 | let immut_access = access.immut_access(); 254 | 255 | let reflect_node = match immut_access.reflect { 256 | Direct(v) => v.immut_reflector(a_self), 257 | Indirect(access) => { 258 | let (sender, receiver) = channel(); 259 | let b_self = a_self.clone(); 260 | 261 | access.indirect(Box::new(move |access| { 262 | let res = Self::reflect(&b_self, access); 263 | let _ = sender.send(res); 264 | })); 265 | 266 | if a_self.synced_thread == std::thread::current().id() { 267 | receiver.recv().unwrap() 268 | } else { 269 | NodeInfo::Hole(Box::new(receiver)).into_node() 270 | } 271 | } 272 | }; 273 | 274 | a_self.used.fetch_add(1, Ordering::SeqCst); 275 | reflect_node 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /interact_prompt/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Interact Prompt 2 | //! 3 | //! In high-level, to use Interact Prompt you need: 4 | //! 5 | //! 1) Deriving of `Interact` over types. 6 | //! 2) Registration of state 7 | //! 3) Spawning or invoking the Interact prompt. 8 | //! 9 | //! In pseudo code: 10 | //! 11 | //! ```ignore 12 | //! extern crate interact; 13 | //! 14 | //! use interact::Interact; 15 | //! use interact_prompt::{LocalRegistry, SendRegistry, Settings}; 16 | //! 17 | //! #[derive(Interact)] 18 | //! struct YourType { 19 | //! // ... 20 | //! } 21 | //! 22 | //! // ... 23 | //! 24 | //! fn in_each_thread(rc: Rc) { 25 | //! // ... You would have some code to register your 'Rc's. 26 | //! LocalRegistry::insert("rc_state", Box::new(rc)); 27 | //! // ... 28 | //! } 29 | //! 30 | //! fn spawn_interact(arc: Arc) { 31 | //! // On the global context you can register any object that is `Send` 32 | //! // and implements `Access` via #[derive(Interact)], this means `Arc` types, 33 | //! SendRegistry::insert("arc_state", Box::new(arc)); 34 | //! 35 | //! interact_prompt::spawn(Settings::default(), ()); 36 | //! } 37 | //! ``` 38 | //! 39 | //! NOTE: Currently only the `SendRegistry` is supported for the background `spawn` variant of 40 | //! Interact. Supporting LocalRegistry is planned for the future. 41 | //! 42 | 43 | #[macro_use] 44 | extern crate lazy_static; 45 | extern crate ansi_term; 46 | extern crate interact; 47 | extern crate rustyline; 48 | 49 | use ansi_term::Color; 50 | use rustyline::completion::Completer; 51 | use rustyline::error::ReadlineError; 52 | use rustyline::highlight::Highlighter; 53 | use rustyline::hint::Hinter; 54 | use rustyline::Helper; 55 | use rustyline::{CompletionType, Config, Context, EditMode, Editor}; 56 | use rustyline::validate::Validator; 57 | use std::borrow::Cow::{self, Borrowed, Owned}; 58 | use std::collections::BTreeMap; 59 | use std::thread; 60 | 61 | use interact::{Assist, NextOptions, NodeTree}; 62 | 63 | mod print; 64 | pub mod registry; 65 | pub use crate::registry::{LocalRegistry, SendRegistry}; 66 | 67 | #[derive(Clone)] 68 | pub struct Settings { 69 | pub history_file: Option, 70 | pub initial_command: Option, 71 | } 72 | 73 | impl Default for Settings { 74 | fn default() -> Self { 75 | Self { 76 | history_file: None, 77 | initial_command: None, 78 | } 79 | } 80 | } 81 | 82 | #[derive(Clone)] 83 | pub enum Interaction { 84 | Line(String), 85 | CtrlC, 86 | CtrlD, 87 | Err, 88 | } 89 | 90 | #[derive(Clone)] 91 | pub enum Response { 92 | Continue, 93 | Exit, 94 | } 95 | 96 | struct Commands { 97 | handlers: BTreeMap<&'static str, Box>, 98 | def_handler: Box, 99 | } 100 | 101 | fn print(elem: &NodeTree) { 102 | print::pretty_format( 103 | elem, 104 | &print::NodePrinterSettings { 105 | max_line_length: 120, 106 | indent_step: 4, 107 | }, 108 | ) 109 | } 110 | 111 | trait Command { 112 | fn handle(&self, commands: &Commands, params: Vec); 113 | fn help(&self) -> &'static [&'static str]; 114 | fn name(&self) -> &'static str; 115 | fn get_completions(&self, line: &str) -> Assist; 116 | } 117 | 118 | struct Help; 119 | 120 | impl Command for Help { 121 | fn handle(&self, commands: &Commands, _params: Vec) { 122 | println!(); 123 | println!("The following are the valid commands:"); 124 | println!(); 125 | 126 | for command in &commands.handlers { 127 | for line in command.1.help() { 128 | println!(" {}", line); 129 | } 130 | } 131 | 132 | println!(); 133 | 134 | registry::with_root(|root| { 135 | println!("Possible nodes to evaluate from:"); 136 | println!(); 137 | for k in root.keys() { 138 | println!(" {}", k); 139 | } 140 | println!(); 141 | }) 142 | } 143 | 144 | fn help(&self) -> &'static [&'static str] { 145 | &[":help Prints this help screen"] 146 | } 147 | fn name(&self) -> &'static str { 148 | ":help" 149 | } 150 | fn get_completions(&self, _line: &str) -> Assist { 151 | Assist::default() 152 | } 153 | } 154 | 155 | struct Exit; 156 | 157 | impl Command for Exit { 158 | fn handle(&self, _commands: &Commands, _params: Vec) { 159 | std::process::exit(0); 160 | } 161 | 162 | fn help(&self) -> &'static [&'static str] { 163 | &[":exit Terminate the program"] 164 | } 165 | fn name(&self) -> &'static str { 166 | ":exit" 167 | } 168 | fn get_completions(&self, _line: &str) -> Assist { 169 | Assist::default() 170 | } 171 | } 172 | 173 | struct Access; 174 | 175 | impl Command for Access { 176 | fn handle(&self, _commands: &Commands, params: Vec) { 177 | let rest_of_string = params.join(" "); 178 | 179 | registry::with_root(|root| { 180 | let res = root.access(&rest_of_string).0; 181 | match res { 182 | Ok(read_value) => { 183 | print(&read_value); 184 | } 185 | Err(err) => { 186 | println!("{:?}", err); 187 | } 188 | } 189 | }) 190 | } 191 | 192 | fn help(&self) -> &'static [&'static str] { 193 | &[" Access the value of expr"] 194 | } 195 | 196 | fn name(&self) -> &'static str { 197 | "" 198 | } 199 | 200 | fn get_completions(&self, line: &str) -> Assist { 201 | registry::with_root(|root| root.probe(line).1) 202 | } 203 | } 204 | 205 | impl Commands { 206 | fn new() -> Self { 207 | let mut handlers = BTreeMap::new(); 208 | 209 | for command in vec![ 210 | Box::new(Help) as Box, 211 | Box::new(Exit) as Box, 212 | ] 213 | .into_iter() 214 | { 215 | handlers.insert(command.name(), command); 216 | } 217 | 218 | Commands { 219 | handlers, 220 | def_handler: Box::new(Access) as Box, 221 | } 222 | } 223 | 224 | fn handle_cmd(&self, line: &str) { 225 | let mut params: Vec = line.split(' ').map(|x| x.to_owned()).collect(); 226 | if params == ["?"] { 227 | Help.handle(&self, params); 228 | } else if params != [""] { 229 | if let Some(command) = self.handlers.get(params[0].as_str()) { 230 | params.remove(0); 231 | (**command).handle(self, params); 232 | } else { 233 | self.def_handler.handle(self, params) 234 | } 235 | } 236 | } 237 | 238 | fn get_next_options(&self, line: &str, pos: usize) -> Assist { 239 | if line == "?" || line == ":" { 240 | let mut assist = Assist::default(); 241 | assist.pend_one(); 242 | return assist; 243 | } 244 | 245 | let split: Vec = line[..pos].split(' ').map(|x| x.to_owned()).collect(); 246 | let prefix = split 247 | .iter() 248 | .filter(|x| x.as_str() != "") 249 | .nth(0) 250 | .cloned() 251 | .unwrap_or_else(|| String::from("")); 252 | 253 | let matching: Vec<_> = self 254 | .handlers 255 | .keys() 256 | .filter(|x| x.starts_with(&prefix)) 257 | .map(|x| String::from(*x)) 258 | .collect(); 259 | if matching.len() == 1 { 260 | let match1 = &matching[0]; 261 | 262 | if let Some(handler) = self.handlers.get(match1.as_str()) { 263 | let mut reconstruct = vec![]; 264 | for i in split.iter() { 265 | if i.trim() != "" && match1.starts_with(i) { 266 | reconstruct.push(match1.clone()); 267 | break; 268 | } 269 | reconstruct.push(i.to_owned()); 270 | } 271 | 272 | let reconstruct = reconstruct.join(" "); 273 | if reconstruct.starts_with(&line[..pos]) { 274 | Assist::default() 275 | .with_valid(pos) 276 | .next_options(NextOptions::Avail( 277 | 0, 278 | vec![String::from(&reconstruct[pos..])], 279 | )) 280 | } else if line[..pos].starts_with(&reconstruct) { 281 | let deeper = String::from(&line[reconstruct.len()..]); 282 | let nospace = deeper 283 | .chars() 284 | .position(|c| c != ' ') 285 | .unwrap_or_else(|| deeper.len()); 286 | let sub_access = (**handler).get_completions(&deeper[nospace..]); 287 | sub_access.with_valid(reconstruct.len() + nospace) 288 | } else { 289 | Assist::default() 290 | } 291 | } else { 292 | Access.get_completions(line) 293 | } 294 | } else if split != [""] { 295 | Access.get_completions(line) 296 | } else { 297 | Assist::default() 298 | } 299 | } 300 | } 301 | 302 | /// This trait defines an optional handler for prompt commands. This allows to 303 | /// override the behavior of the handler for `()`. 304 | pub trait Handler { 305 | fn receive_interaction(&self, intr: Interaction) -> Response { 306 | match intr { 307 | Interaction::Line(string) => { 308 | Commands::new().handle_cmd(&string); 309 | } 310 | Interaction::CtrlC | Interaction::CtrlD => { 311 | std::process::exit(0); 312 | } 313 | _ => {} 314 | } 315 | Response::Continue 316 | } 317 | } 318 | 319 | // InteractPromptHelper 320 | 321 | struct InteractPromptHelper<'a, H>((), &'a H) 322 | where 323 | H: 'a; 324 | 325 | impl<'a, H> Completer for InteractPromptHelper<'a, H> 326 | where 327 | H: 'a, 328 | { 329 | type Candidate = String; 330 | 331 | fn complete(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> Result<(usize, Vec), ReadlineError> { 332 | let (valid, _, _, options) = Commands::new().get_next_options(line, pos).dismantle(); 333 | Ok(options.into_position(valid)) 334 | } 335 | } 336 | 337 | impl<'a, H> Hinter for InteractPromptHelper<'a, H> { 338 | fn hint(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> Option { 339 | let (valid, _, _, options) = Commands::new().get_next_options(line, pos).dismantle(); 340 | let (from_pos, v) = options.into_position(valid); 341 | if v.len() == 1 { 342 | if from_pos < pos { 343 | let v0_len = v[0].len(); 344 | Some(v[0][std::cmp::min(pos - from_pos, v0_len)..].to_owned()) 345 | } else { 346 | Some(v[0].to_owned()) 347 | } 348 | } else { 349 | None 350 | } 351 | } 352 | } 353 | 354 | impl<'a, H> Highlighter for InteractPromptHelper<'a, H> { 355 | fn highlight_prompt<'b, 's: 'b, 'p: 'b>(&'s self, prompt: &'p str, _default: bool) -> Cow<'b, str> { 356 | Borrowed(prompt) 357 | } 358 | 359 | fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { 360 | let s = format!("{}", Color::Fixed(240).paint(hint)); 361 | Owned(s.to_owned()) 362 | } 363 | 364 | fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { 365 | let (valid, pending, pending_valid, _) = 366 | Commands::new().get_next_options(line, pos).dismantle(); 367 | let yellow_cutoff = std::cmp::min(valid, line.len()); 368 | let green_cutoff = std::cmp::min(valid + pending - pending_valid, line.len()); 369 | let red_cutoff = std::cmp::min(valid + pending, line.len()); 370 | 371 | let ok = &line[..yellow_cutoff].to_string(); 372 | let pending = format!( 373 | "{}", 374 | Color::Yellow.paint(&line[yellow_cutoff..green_cutoff]) 375 | ); 376 | let pending_valid = format!( 377 | "{}", 378 | Color::Green.bold().paint(&line[green_cutoff..red_cutoff]) 379 | ); 380 | let err = format!("{}", Color::Red.paint(&line[red_cutoff..])); 381 | 382 | Owned(format!("{}{}{}{}", ok, pending, pending_valid, err)) 383 | } 384 | 385 | fn highlight_char(&self, _grapheme: &str, _pos: usize) -> bool { 386 | false 387 | } 388 | } 389 | 390 | impl<'a, H> Helper for InteractPromptHelper<'a, H> {} 391 | 392 | impl<'a, H> Validator for InteractPromptHelper<'a, H> {} 393 | 394 | impl Handler for () {} 395 | 396 | #[derive(Debug)] 397 | pub enum PromptError { 398 | ReadLine(rustyline::error::ReadlineError), 399 | } 400 | 401 | /// Use the current thread for an interactive `Interact` prompt. 402 | pub fn direct(settings: Settings, handler: H) -> Result<(), PromptError> 403 | where 404 | H: Handler 405 | { 406 | let config = Config::builder() 407 | .history_ignore_space(true) 408 | .completion_type(CompletionType::List) 409 | .edit_mode(EditMode::Emacs) 410 | .build(); 411 | let mut rl = Editor::with_config(config); 412 | 413 | let Settings { 414 | history_file, 415 | initial_command, 416 | } = settings; 417 | let h = InteractPromptHelper((), &handler); 418 | rl.set_helper(Some(h)); 419 | 420 | println!("Rust `interact`, type '?' for more information"); 421 | 422 | if let Some(history_file) = &history_file { 423 | rl.load_history(history_file) 424 | .map_err(PromptError::ReadLine)?; 425 | } 426 | 427 | match initial_command { 428 | None => {} 429 | Some(initial_command) => { 430 | println!("{}", initial_command); 431 | let response = handler.receive_interaction(Interaction::Line(initial_command)); 432 | match response { 433 | Response::Exit => return Ok(()), 434 | Response::Continue => {} 435 | } 436 | } 437 | } 438 | 439 | loop { 440 | let prompt = format!("{} ", Color::Fixed(240).bold().paint(">>>")); 441 | let line = rl.readline(&prompt); 442 | 443 | let interaction = match line { 444 | Ok(line) => { 445 | rl.add_history_entry(&line); 446 | Interaction::Line(line) 447 | } 448 | Err(ReadlineError::Interrupted) => Interaction::CtrlC, 449 | Err(ReadlineError::Eof) => Interaction::CtrlD, 450 | Err(_err) => { 451 | Interaction::Err // TODO 452 | } 453 | }; 454 | 455 | let response = handler.receive_interaction(interaction); 456 | match response { 457 | Response::Exit => break, 458 | Response::Continue => {} 459 | } 460 | } 461 | 462 | if let Some(history_file) = &history_file { 463 | rl.save_history(history_file) 464 | .map_err(PromptError::ReadLine)?; 465 | } 466 | 467 | Ok(()) 468 | } 469 | 470 | /// Spawn `Interact` in a new thread. 471 | pub fn spawn(settings: Settings, handler: H) -> std::thread::JoinHandle<()> 472 | where 473 | H: Handler + Send + Sync + 'static, 474 | { 475 | thread::spawn(move || { 476 | let _ = direct(settings, handler); 477 | }) 478 | } 479 | -------------------------------------------------------------------------------- /interact/src/climber.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::cell::RefCell; 3 | use std::rc::Rc; 4 | use std::sync::mpsc::{channel, Sender}; 5 | use std::sync::{Arc, Mutex}; 6 | 7 | use crate::access::derive::{ReflectEnum, ReflectStruct, StructKind}; 8 | use crate::deser; 9 | use crate::reflector::Reflector; 10 | use crate::{ 11 | Access, CallError, ExpectTree, Function, NodeInfo, NodeTree, ReflectMut, Token, TokenKind, 12 | TokenVec, 13 | }; 14 | use crate::{Assist, NextOptions}; 15 | 16 | #[derive(Debug)] 17 | pub enum ClimbError { 18 | AssignError(crate::access::AssignError), 19 | Borrowed, 20 | BorrowedMut, 21 | CallError(crate::access::CallError), 22 | DeserError(crate::deser::DeserError), 23 | TokenError(crate::tokens::Error), 24 | Indirect, 25 | Locked, 26 | MissingStartComponent, 27 | NeedMutPath, 28 | NotFound, 29 | NullPath, 30 | UnattainedMutability, 31 | UnexpectedExpressionEnd, 32 | UnexpectedToken, 33 | } 34 | 35 | /// Climber represents the full state of evaluation of Interact expressions. It is used within the 36 | /// impls of the `Access` trait, and most likely does not require direct references from Interact's 37 | /// crate users, unless manually providing impls of `Access` not via the `#[derive(Interact)]`. 38 | #[derive(Clone)] 39 | pub struct Climber<'a> { 40 | probe_only: bool, 41 | reflector: Arc, 42 | expect: ExpectTree>, 43 | tokenvec: TokenVec<'a>, 44 | valid_pos: usize, 45 | sender: Option>>, Result)>>, 46 | } 47 | 48 | #[doc(hidden)] 49 | pub enum EnumOrStruct<'a> { 50 | Enum(&'a dyn ReflectEnum), 51 | Struct(&'a dyn ReflectStruct), 52 | } 53 | 54 | #[doc(hidden)] 55 | pub enum EnumOrStructMut<'a, 'b> { 56 | Enum(&'b mut dyn ReflectEnum), 57 | Struct(&'a mut dyn ReflectStruct), 58 | } 59 | 60 | macro_rules! if_mut { 61 | (mut, {$t: expr} else {$f:expr}) => { 62 | $t 63 | }; 64 | (immut, {$t: expr} else {$f:expr}) => { 65 | $f 66 | }; 67 | (mut, {$t: pat} else {$f:pat}) => { 68 | $t 69 | }; 70 | (immut, {$t: pat} else {$f:pat}) => { 71 | $f 72 | }; 73 | } 74 | 75 | macro_rules! climber_impl { 76 | (check_field_access, $mut:ident, $recurse:ident, $get_field_by_idx:ident, 77 | $get_field_by_name:ident, $general_access:ident, $get_variant_struct:ident, $EnumOrStruct:ident, 78 | $self:ident, $reflect:ident) => 79 | { { 80 | let mut prefix : String = "".to_owned(); 81 | 82 | enum T { Struct(A), Enum(B) }; 83 | 84 | let p_match = match if_mut!($mut, { &mut $reflect } else { $reflect }) { 85 | $EnumOrStruct::Struct(p_struct) => { T::Struct(p_struct) } 86 | $EnumOrStruct::Enum(p_enum) => { T::Enum(p_enum) } 87 | }; 88 | 89 | if !$self.tokenvec.is_empty() { 90 | if let TokenKind::FieldAccess = &$self.tokenvec.top_kind() { 91 | $self.tokenvec.advance(1); 92 | } else { 93 | $self.expect_token(TokenKind::FieldAccess, Cow::Borrowed(".")); 94 | return Ok(None); 95 | } 96 | 97 | if $self.tokenvec.remaining() >= 1 { 98 | if let TokenKind::Ident = &$self.tokenvec.top_kind() { 99 | prefix = String::from($self.tokenvec.top().text.as_ref()); 100 | } else if let TokenKind::NonNegativeDecimal(nnd) = &$self.tokenvec.top_kind() { 101 | prefix = format!("{}", nnd); 102 | } else { 103 | return Err(ClimbError::UnexpectedToken); 104 | } 105 | } 106 | } else { 107 | $self.expect_token(TokenKind::FieldAccess, Cow::Borrowed(".")); 108 | }; 109 | 110 | match p_match { 111 | T::Struct(p_struct) => { 112 | let desc = p_struct.get_desc(); 113 | 114 | match desc.kind { 115 | StructKind::Tuple(i) => { 116 | for j in 0..i { 117 | let name = format!("{}", j); 118 | if name == prefix { 119 | let field = 120 | p_struct.$get_field_by_idx(j).unwrap(); 121 | $self.tokenvec.advance(1); 122 | return $self.$general_access( 123 | if_mut!($mut, { &mut *field } else { &*field }) 124 | ).map(Some) 125 | } 126 | if name.starts_with(prefix.as_str()) { 127 | $self.expect_token(TokenKind::Ident, Cow::Owned(name)); 128 | $self.expect.retract_one(); 129 | } 130 | } 131 | }, 132 | StructKind::Fields(names) => { 133 | for name in names { 134 | if *name == prefix { 135 | let field = 136 | p_struct.$get_field_by_name(name).unwrap(); 137 | $self.tokenvec.advance(1); 138 | return $self.$general_access( 139 | if_mut!($mut, { &mut *field } else { &*field }) 140 | ).map(Some) 141 | } 142 | if name.starts_with(prefix.as_str()) { 143 | $self.expect_token(TokenKind::Ident, Cow::Borrowed(name)); 144 | $self.expect.retract_one(); 145 | } 146 | } 147 | } 148 | StructKind::Unit => {}, 149 | } 150 | } 151 | T::Enum(p_enum) => { 152 | let desc = p_enum.get_variant_struct().get_desc(); 153 | let p_struct = p_enum.$get_variant_struct(); 154 | if desc.name == prefix { 155 | $self.tokenvec.advance(1); 156 | if let Some(sub) = $self.$recurse( 157 | if_mut!($mut, { 158 | $EnumOrStruct::Struct(p_struct) 159 | } else { 160 | &$EnumOrStruct::Struct(p_struct) 161 | }) 162 | )? { 163 | return Ok(Some(sub)); 164 | } else { 165 | return Ok(Some(Reflector::reflect_struct(&$self.reflector, &desc, p_struct, true))) 166 | } 167 | } else { 168 | if desc.name.starts_with(prefix.as_str()) { 169 | $self.expect_token(TokenKind::Ident, Cow::Borrowed(desc.name)); 170 | $self.expect.retract_one(); 171 | } 172 | } 173 | } 174 | } 175 | 176 | Ok(None) 177 | } }; 178 | 179 | (check_functions, $mut:ident, $self:ident, $functions:ident, $dynvalue:ident, $immut_call:ident) => 180 | { { 181 | if $functions.is_empty() { 182 | return Ok(None); 183 | } 184 | 185 | let mut prefix : String = "".to_owned(); 186 | 187 | if !$self.tokenvec.is_empty() { 188 | if let TokenKind::FieldAccess = &$self.tokenvec.top_kind() { 189 | $self.tokenvec.step(); 190 | if !$self.tokenvec.is_empty() { 191 | if let TokenKind::Ident = &$self.tokenvec.top_kind() { 192 | prefix = String::from($self.tokenvec.top().text.as_ref()); 193 | } else { 194 | return Err(ClimbError::UnexpectedToken); 195 | } 196 | } 197 | } else { 198 | return Ok(None); 199 | } 200 | } else { 201 | $self.expect_token(TokenKind::FieldAccess, Cow::Borrowed(".")); 202 | }; 203 | 204 | for function in $functions { 205 | if function.name == prefix { 206 | $self.tokenvec.advance(1); 207 | if $self.tokenvec.is_empty() { 208 | $self.expect_token(TokenKind::TupleOpen, Cow::Borrowed("(")); 209 | return Ok(None); 210 | } 211 | 212 | if let TokenKind::TupleOpen = &$self.tokenvec.top_kind() { 213 | let retval : Rc, ClimbError>>>> = 214 | Rc::new(RefCell::new(None)); 215 | let retval2 = retval.clone(); 216 | 217 | let call_res = $dynvalue.$immut_call( 218 | function.name, $self, 219 | Box::new(move |access, climber| { 220 | let mut retval = retval2.borrow_mut(); 221 | *retval = Some(climber.general_access_immut(access).map(Some)); 222 | })); 223 | 224 | match call_res { 225 | Err(CallError::NeedMutable) => { 226 | if_mut!($mut, { { 227 | return Err(ClimbError::UnattainedMutability); 228 | } } else { { 229 | return Err(ClimbError::NeedMutPath); 230 | } }); 231 | } 232 | Err(e) => { 233 | return Err(ClimbError::CallError(e)); 234 | } 235 | Ok(()) => { 236 | match retval.borrow_mut().take(){ 237 | Some(retval) => { return retval } 238 | None => {} 239 | } 240 | return Ok(None); 241 | } 242 | } 243 | } else { 244 | return Err(ClimbError::UnexpectedToken); 245 | } 246 | } 247 | 248 | if function.name.starts_with(prefix.as_str()) { 249 | $self.expect_token(TokenKind::Ident, Cow::Borrowed(function.name)); 250 | $self.expect_token(TokenKind::TupleOpen, Cow::Borrowed("(")); 251 | $self.expect.retract_one(); 252 | $self.expect.retract_one(); 253 | } 254 | } 255 | 256 | Ok(None) 257 | } }; 258 | 259 | (indirect_call, $mut:ident, $self:ident, $access:ident, $indirect:ident, $fname:ident) => 260 | { { 261 | let mut climber = 262 | Climber { 263 | probe_only: $self.probe_only, 264 | reflector: $self.reflector.clone(), 265 | expect: $self.expect.clone(), 266 | valid_pos: $self.valid_pos, 267 | sender: None, 268 | tokenvec: $self.tokenvec.clone_owned(), 269 | }; 270 | 271 | let recv = if $self.sender.is_none() { 272 | let (sender, receiver) = channel(); 273 | climber.sender = Some(sender); 274 | Some(receiver) 275 | } else { 276 | climber.sender = $self.sender.take(); 277 | None 278 | }; 279 | 280 | let clone_ref = Arc::new(Mutex::new(climber)); 281 | 282 | $access.$indirect(Box::new(move |access| { 283 | let clone_send = clone_ref.clone(); 284 | let mut clone = clone_ref.lock().unwrap(); 285 | let res = clone.$fname(access); 286 | if let Err(ClimbError::Indirect) = res { 287 | return; 288 | } else { 289 | let sender = clone.sender.take().unwrap(); 290 | let _ = sender.send((clone_send, res)); 291 | } 292 | })); 293 | 294 | match recv { 295 | Some(recv) => { 296 | let (clone_ref, res) = recv.recv().unwrap(); 297 | let mut clone = clone_ref.lock().unwrap(); 298 | std::mem::swap(&mut $self.expect, &mut clone.expect); 299 | $self.tokenvec.take_pos(clone.tokenvec.pos()); 300 | $self.valid_pos = clone.valid_pos; 301 | return res; 302 | } 303 | None => { 304 | return Err(ClimbError::Indirect); 305 | } 306 | } 307 | } }; 308 | } 309 | 310 | impl<'a> Climber<'a> { 311 | pub fn new(max_nodes: usize, probe_only: bool, tokens: &'a [Token<'a>]) -> Self { 312 | Self { 313 | probe_only, 314 | tokenvec: TokenVec::new(tokens), 315 | reflector: Reflector::new(max_nodes), 316 | expect: ExpectTree::new(), 317 | valid_pos: 0, 318 | sender: None, 319 | } 320 | } 321 | 322 | pub fn general_access_immut<'b>( 323 | &mut self, 324 | dynvalue: &'b dyn Access, 325 | ) -> Result { 326 | use crate::Reflect::*; 327 | 328 | self.expect = ExpectTree::new(); 329 | self.valid_pos = self.tokenvec.pos(); 330 | 331 | if !self.tokenvec.is_empty() { 332 | if let TokenKind::Assign = &self.tokenvec.top_kind() { 333 | return Err(ClimbError::NeedMutPath); 334 | } 335 | if let TokenKind::Asterix = &self.tokenvec.top_kind() { 336 | self.tokenvec.advance(1); 337 | } 338 | } 339 | 340 | let immut_access = dynvalue.immut_access(); 341 | 342 | let save_tokenvec = self.tokenvec.clone(); 343 | 344 | if let Some(opt_val) = self.check_functions_immut(immut_access.functions, dynvalue)? { 345 | return Ok(opt_val); 346 | } 347 | self.expect.retract_path(0); 348 | let pos = self.tokenvec.pos(); 349 | self.tokenvec = save_tokenvec.clone(); 350 | 351 | let pos = match immut_access.reflect { 352 | Direct(access) => { 353 | if let Some(opt_val) = access.immut_climber(self)? { 354 | return Ok(opt_val); 355 | } 356 | self.expect.retract_path(0); 357 | let pos = std::cmp::max(self.tokenvec.pos(), pos); 358 | self.tokenvec = save_tokenvec.clone(); 359 | pos 360 | } 361 | Indirect(access) => { 362 | climber_impl!(indirect_call, mut, self, access, indirect, general_access_immut); 363 | } 364 | }; 365 | 366 | self.tokenvec.take_pos(pos); 367 | if self.tokenvec.remaining() > 0 { 368 | return Err(ClimbError::UnexpectedToken); 369 | } 370 | 371 | Ok(Reflector::reflect(&self.reflector, dynvalue)) 372 | } 373 | 374 | pub fn general_access_mut<'b>( 375 | &mut self, 376 | dynvalue: &'b mut dyn Access, 377 | ) -> Result { 378 | self.expect = ExpectTree::new(); 379 | self.valid_pos = self.tokenvec.pos(); 380 | 381 | if !self.tokenvec.is_empty() { 382 | if let TokenKind::Assign = &self.tokenvec.top_kind() { 383 | self.tokenvec.advance(1); 384 | 385 | let probe_only = self.probe_only; 386 | let mut tracker = self.borrow_tracker(); 387 | let res = dynvalue.mut_assign(&mut tracker, probe_only); 388 | return match res { 389 | Ok(()) => { 390 | self.valid_pos = self.tokenvec.pos(); 391 | Ok(NodeInfo::Leaf(Cow::Borrowed("")).into_node()) 392 | } 393 | Err(e) => Err(ClimbError::AssignError(e)), 394 | }; 395 | } 396 | if let TokenKind::Asterix = &self.tokenvec.top_kind() { 397 | self.tokenvec.advance(1); 398 | } 399 | } 400 | 401 | let functions = { dynvalue.mut_access().functions }; 402 | 403 | let save_tokenvec = self.tokenvec.clone(); 404 | if let Some(opt_val) = self.check_functions_mut(functions, dynvalue)? { 405 | return Ok(opt_val); 406 | } 407 | let pos = self.tokenvec.pos(); 408 | self.tokenvec = save_tokenvec.clone(); 409 | self.expect.retract_path(0); 410 | 411 | let pos = { 412 | let mut mut_access = dynvalue.mut_access(); 413 | match mut_access.reflect { 414 | ReflectMut::Direct(ref mut access) => { 415 | if let Some(opt_val) = access.mut_climber(self)? { 416 | return Ok(opt_val); 417 | } 418 | self.expect.retract_path(0); 419 | let pos = std::cmp::max(self.tokenvec.pos(), pos); 420 | self.tokenvec = save_tokenvec.clone(); 421 | pos 422 | } 423 | ReflectMut::Indirect(access) => { 424 | climber_impl!(indirect_call, mut, self, access, indirect_mut, general_access_mut); 425 | } 426 | _ => pos, 427 | } 428 | }; 429 | 430 | self.tokenvec.take_pos(pos); 431 | 432 | if self.tokenvec.remaining() > 0 { 433 | return Err(ClimbError::UnexpectedToken); 434 | } 435 | 436 | Ok(Reflector::reflect(&self.reflector, dynvalue)) 437 | } 438 | 439 | fn check_functions_immut<'b>( 440 | &mut self, 441 | functions: &'static [Function], 442 | dynvalue: &'b dyn Access, 443 | ) -> Result, ClimbError> { 444 | climber_impl!( 445 | check_functions, 446 | immut, 447 | self, 448 | functions, 449 | dynvalue, 450 | immut_call 451 | ) 452 | } 453 | 454 | fn check_functions_mut<'b>( 455 | &mut self, 456 | functions: &'static [Function], 457 | dynvalue: &'b mut dyn Access, 458 | ) -> Result, ClimbError> { 459 | climber_impl!(check_functions, mut, self, functions, dynvalue, mut_call) 460 | } 461 | 462 | pub fn check_field_access_immut<'c, 'b>( 463 | &mut self, 464 | reflect: &'c EnumOrStruct<'b>, 465 | ) -> Result, ClimbError> { 466 | climber_impl!( 467 | check_field_access, 468 | immut, 469 | check_field_access_immut, 470 | get_field_by_idx, 471 | get_field_by_name, 472 | general_access_immut, 473 | get_variant_struct, 474 | EnumOrStruct, 475 | self, 476 | reflect 477 | ) 478 | } 479 | 480 | pub fn check_field_access_mut<'c, 'b>( 481 | &mut self, 482 | mut reflect: EnumOrStructMut<'c, 'b>, 483 | ) -> Result, ClimbError> { 484 | climber_impl!(check_field_access, mut, check_field_access_mut, 485 | get_field_by_idx_mut, get_field_by_name_mut, general_access_mut, get_variant_struct_mut, 486 | EnumOrStructMut, self, reflect) 487 | } 488 | 489 | fn expect_token(&mut self, kind: TokenKind, text: Cow<'static, str>) { 490 | self.expect.advance(Token { 491 | kind, 492 | space_diff: 0, 493 | text, 494 | }); 495 | } 496 | 497 | pub fn mutex_handling<'b>( 498 | &mut self, 499 | m: &'b std::sync::Mutex, 500 | ) -> Result { 501 | let save = self.clone(); 502 | let retval = { 503 | match m.try_lock() { 504 | Ok(locked) => self.general_access_immut(&*locked), 505 | Err(_) => return Err(ClimbError::Locked), 506 | } 507 | }; 508 | 509 | if let Err(ClimbError::NeedMutPath) = &retval { 510 | *self = save; 511 | match m.try_lock() { 512 | Ok(mut locked) => return self.general_access_mut(&mut *locked), 513 | Err(_) => return Err(ClimbError::Locked), 514 | } 515 | } else { 516 | return retval; 517 | } 518 | } 519 | 520 | pub fn refcell_handling<'b>( 521 | &mut self, 522 | m: &'b std::cell::RefCell, 523 | ) -> Result { 524 | let save = self.clone(); 525 | let retval = { 526 | match m.try_borrow() { 527 | Ok(borrowed) => self.general_access_immut(&*borrowed), 528 | Err(_) => return Err(ClimbError::Borrowed), 529 | } 530 | }; 531 | if let Err(ClimbError::NeedMutPath) = &retval { 532 | *self = save; 533 | match m.try_borrow_mut() { 534 | Ok(mut borrowed) => self.general_access_mut(&mut *borrowed), 535 | Err(_) => return Err(ClimbError::BorrowedMut), 536 | } 537 | } else { 538 | return retval; 539 | } 540 | } 541 | 542 | pub fn open_bracket(&mut self) -> bool { 543 | if self.tokenvec.is_empty() { 544 | self.expect_token(TokenKind::SubscriptOpen, Cow::Borrowed("[")); 545 | return false; 546 | } 547 | 548 | if let TokenKind::SubscriptOpen = &self.tokenvec.top_kind() { 549 | self.tokenvec.advance(1); 550 | true 551 | } else { 552 | false 553 | } 554 | } 555 | 556 | pub fn close_bracket(&mut self) -> Result<(), ClimbError> { 557 | if self.tokenvec.is_empty() { 558 | self.expect_token(TokenKind::TupleClose, Cow::Borrowed("]")); 559 | return Err(ClimbError::UnexpectedExpressionEnd); 560 | } 561 | 562 | if let TokenKind::SubscriptClose = &self.tokenvec.top_kind() { 563 | self.tokenvec.advance(1); 564 | Ok(()) 565 | } else { 566 | Err(ClimbError::UnexpectedToken) 567 | } 568 | } 569 | 570 | pub fn is_probe_only(&self) -> bool { 571 | self.probe_only 572 | } 573 | 574 | pub fn convert_to_assist(mut self) -> (Assist>>, usize) { 575 | let mut assist = Assist::default(); 576 | assist.pend(self.valid_pos); 577 | assist.commit_pending(); 578 | 579 | let flatten = self.expect.into_flatten(); 580 | let mut pending_partial = 0; 581 | 582 | if !self.tokenvec.is_empty() { 583 | let text = &self.tokenvec.top().text; 584 | let mut good = !flatten.is_empty(); 585 | for opt in &flatten { 586 | if !opt[0].text.starts_with(text.as_ref()) { 587 | good = false; 588 | break; 589 | } 590 | } 591 | if good { 592 | pending_partial = text.len(); 593 | self.tokenvec.advance(1); 594 | } 595 | } 596 | 597 | assist.pend(self.tokenvec.pos() - self.valid_pos); 598 | assist.set_next_options(NextOptions::Avail(assist.pending(), flatten)); 599 | (assist, pending_partial) 600 | } 601 | 602 | pub fn borrow_tracker<'b>(&'b mut self) -> deser::Tracker<'a, 'b> { 603 | deser::Tracker::new(&mut self.expect, &mut self.tokenvec) 604 | } 605 | } 606 | --------------------------------------------------------------------------------