Graph and Node controllers stub and mock implementations (https://github.com/enso-org/ide/pull/262)

For early design / API review.
Authored by michal.urbanczyk@luna-lang.org

Original commit: 461e6ae780
This commit is contained in:
Michał Wawrzyniec Urbańczyk 2020-03-18 08:43:52 +01:00 committed by GitHub
parent ef070434f3
commit 83c66d265a
14 changed files with 484 additions and 57 deletions

View File

@ -172,6 +172,8 @@ impl Clone for Ast {
} }
} }
impl CloneRef for Ast {}
/// `IntoIterator` for `&Ast` that just delegates to `&Shape`'s `IntoIterator`. /// `IntoIterator` for `&Ast` that just delegates to `&Shape`'s `IntoIterator`.
impl<'t> IntoIterator for &'t Ast { impl<'t> IntoIterator for &'t Ast {
type Item = <&'t Shape<Ast> as IntoIterator>::Item; type Item = <&'t Shape<Ast> as IntoIterator>::Item;
@ -181,6 +183,24 @@ impl<'t> IntoIterator for &'t Ast {
} }
} }
impl ToString for Ast {
fn to_string(&self) -> String {
self.repr()
}
}
impl From<Ast> for String {
fn from(ast:Ast) -> Self {
ast.to_string()
}
}
impl From<&Ast> for String {
fn from(ast:&Ast) -> Self {
ast.to_string()
}
}
impl Ast { impl Ast {
pub fn shape(&self) -> &Shape<Ast> { pub fn shape(&self) -> &Shape<Ast> {
self self
@ -916,37 +936,46 @@ impl Ast {
// TODO smart constructors for other cases // TODO smart constructors for other cases
// as part of https://github.com/luna/enso/issues/338 // as part of https://github.com/luna/enso/issues/338
/// Creates an Ast node with Number inside.
pub fn number(number:i64) -> Ast { pub fn number(number:i64) -> Ast {
let number = Number {base:None,int:number.to_string()}; let number = Number {base:None,int:number.to_string()};
Ast::from(number) Ast::from(number)
} }
pub fn cons<Str: ToString>(name:Str) -> Ast { /// Creates an Ast node with Cons inside.
pub fn cons<Str:ToString>(name:Str) -> Ast {
let cons = Cons {name:name.to_string()}; let cons = Cons {name:name.to_string()};
Ast::from(cons) Ast::from(cons)
} }
pub fn var<Str: ToString>(name:Str) -> Ast { /// Creates an Ast node with Var inside and given ID.
pub fn var_with_id<Name:Str>(name:Name, id:ID) -> Ast {
let name = name.into();
let var = Var{name};
Ast::new(var,Some(id))
}
pub fn var<Str:ToString>(name:Str) -> Ast {
let var = Var{name:name.to_string()}; let var = Var{name:name.to_string()};
Ast::from(var) Ast::from(var)
} }
pub fn opr<Str: ToString>(name:Str) -> Ast { pub fn opr<Str:ToString>(name:Str) -> Ast {
let opr = Opr{name:name.to_string() }; let opr = Opr{name:name.to_string() };
Ast::from(opr) Ast::from(opr)
} }
pub fn prefix<Func:Into<Ast>, Arg:Into<Ast>>(func:Func, arg:Arg) -> Ast { pub fn prefix<Func:Into<Ast>, Arg:Into<Ast>>(func:Func, arg:Arg) -> Ast {
let off = 1; let off = 1;
let opr = Prefix{ func:func.into(), off, arg:arg.into() }; let opr = Prefix { func:func.into(), off, arg:arg.into() };
Ast::from(opr) Ast::from(opr)
} }
/// Creates an AST node with `Infix` shape, where both its operands are Vars. /// Creates an AST node with `Infix` shape, where both its operands are Vars.
pub fn infix_var<Str0, Str1, Str2>(larg:Str0, opr:Str1, rarg:Str2) -> Ast pub fn infix_var<Str0,Str1,Str2>(larg:Str0, opr:Str1, rarg:Str2) -> Ast
where Str0: ToString where Str0 : ToString
, Str1: ToString , Str1 : ToString
, Str2: ToString { , Str2 : ToString {
let larg = Ast::var(larg); let larg = Ast::var(larg);
let loff = 1; let loff = 1;
let opr = Ast::opr(opr); let opr = Ast::opr(opr);

View File

@ -15,10 +15,8 @@
//! Controllers store their handles using `utils::cell` handle types to ensure //! Controllers store their handles using `utils::cell` handle types to ensure
//! that mutable state is safely accessed. //! that mutable state is safely accessed.
pub mod text; pub mod graph;
pub mod module; pub mod module;
pub mod notification; pub mod notification;
pub mod project; pub mod project;
pub mod text;
/// General-purpose `Result` supporting any `Error`-compatible failures.
pub type FallibleResult<T> = Result<T,failure::Error>;

View File

@ -0,0 +1,301 @@
//! Graph Controller.
//!
//! This controller provides access to a specific graph. It lives under a module controller, as
//! each graph belongs to some module.
use crate::prelude::*;
pub use crate::double_representation::graph::Id;
use flo_stream::MessagePublisher;
use flo_stream::Subscriber;
use utils::channel::process_stream_with_handle;
// ==============
// === Errors ===
// ==============
/// Error raised when node with given Id was not found in the graph's body.
#[derive(Clone,Copy,Debug,Fail)]
#[fail(display="Node with Id {} was not found.", _0)]
pub struct NodeNotFound(ast::ID);
// ============
// === Node ===
// ============
/// TODO: replace with usage of the structure to be provided by Josef
#[derive(Clone,Copy,Debug)]
pub struct NodeMetadata; // here goes position
/// Description of the node with all information available to the graph controller.
#[derive(Clone,Debug)]
pub struct Node {
/// Information based on AST, from double_representation module.
pub info : double_representation::node::NodeInfo,
/// Information about this node stored in the module's metadata.
pub metadata : Option<NodeMetadata>,
}
// ===================
// === NewNodeInfo ===
// ===================
/// Describes the node to be added.
#[derive(Clone,Debug)]
pub struct NewNodeInfo {
/// Expression to be placed on the node
pub expression : String,
/// Visual node position in the graph scene.
pub metadata : Option<NodeMetadata>,
/// ID to be given to the node.
pub id : Option<ast::ID>,
/// Where line created by adding this node should appear.
pub location_hint : LocationHint
}
/// Describes the desired position of the node's line in the graph's code block.
#[derive(Clone,Copy,Debug)]
pub enum LocationHint {
/// Try placing this node's line before the line described by id.
Before(ast::ID),
/// Try placing this node's line after the line described by id.
After(ast::ID),
/// Try placing this node's line at the start of the graph's code block.
Start,
/// Try placing this node's line at the end of the graph's code block.
End,
}
// ==================
// === Controller ===
// ==================
/// Handle providing graph controller interface.
#[derive(Clone,Debug)]
pub struct Handle {
/// Controller of the module which this graph belongs to.
module : controller::module::Handle,
id : Id,
/// Publisher. When creating a controller, it sets up task to emit notifications through this
/// publisher to relay changes from the module controller.
// TODO: [mwu] Remove in favor of streams mapping over centralized module-scope publisher
publisher : Rc<RefCell<controller::notification::Publisher<controller::notification::Graph>>>,
}
impl Handle {
/// Gets a handle to a controller of the module that this definition belongs to.
pub fn module(&self) -> controller::module::Handle {
self.module.clone_ref()
}
/// Gets a handle to a controller of the module that this definition belongs to.
pub fn id(&self) -> Id {
self.id.clone()
}
/// Creates a new controller. Does not check if id is valid.
///
/// Requires global executor to spawn the events relay task.
pub fn new_unchecked(module:controller::module::Handle, id:Id) -> Handle {
let graphs_notifications = module.subscribe_graph_notifications();
let publisher = default();
let ret = Handle {module,id,publisher};
let weak = Rc::downgrade(&ret.publisher);
let relay_notifications = process_stream_with_handle(graphs_notifications,weak,
|notification,this| {
match notification {
controller::notification::Graphs::Invalidate =>
this.borrow_mut().publish(controller::notification::Graph::Invalidate),
}
});
executor::global::spawn(relay_notifications);
ret
}
/// Creates a new graph controller. Given ID should uniquely identify a definition in the
/// module. Fails if ID cannot be resolved.
///
/// Requires global executor to spawn the events relay task.
pub fn new(module:controller::module::Handle, id:Id) -> FallibleResult<Handle> {
let ret = Self::new_unchecked(module,id);
// Get and discard definition info, we are just making sure it can be obtained.
let _ = ret.graph_definition_info()?;
Ok(ret)
}
/// Retrieves double rep information about definition providing this graph.
pub fn graph_definition_info
(&self) -> FallibleResult<double_representation::definition::DefinitionInfo> {
let module = self.module();
let id = self.id();
module.find_definition(&id)
}
/// Returns double rep information about all nodes in the graph.
pub fn all_node_infos
(&self) -> FallibleResult<Vec<double_representation::node::NodeInfo>> {
let definition = self.graph_definition_info()?;
let graph = double_representation::graph::GraphInfo::from_definition(definition);
Ok(graph.nodes)
}
/// Retrieves double rep information about node with given ID.
pub fn node_info
(&self, id:ast::ID) -> FallibleResult<double_representation::node::NodeInfo> {
let nodes = self.all_node_infos()?;
let node = nodes.into_iter().find(|node_info| node_info.id() == id);
node.ok_or_else(|| NodeNotFound(id).into())
}
/// Gets information about node with given id.
///
/// Note that it is more efficient to use `get_nodes` to obtain all information at once,
/// rather then repeatedly call this method.
pub fn node(&self, id:ast::ID) -> FallibleResult<Node> {
let info = self.node_info(id)?;
let metadata = self.node_metadata(id).ok();
Ok(Node {info,metadata})
}
/// Returns information about all the nodes currently present in this graph.
pub fn nodes(&self) -> FallibleResult<Vec<Node>> {
let node_infos = self.all_node_infos()?;
let mut nodes = Vec::new();
for info in node_infos {
let metadata = self.node_metadata(info.id()).ok();
nodes.push(Node {info,metadata})
}
Ok(nodes)
}
/// Adds a new node to the graph and returns information about created node.
pub fn add_node(&self, _node:NewNodeInfo) -> FallibleResult<Node> {
todo!()
}
/// Removes the node with given Id.
pub fn remove_node(&self, _id:ast::ID) -> FallibleResult<()> {
todo!()
}
/// Subscribe to updates about changes in this graph.
pub fn subscribe(&self) -> Subscriber<controller::notification::Graph> {
self.publisher.borrow_mut().0.subscribe()
}
/// Retrieves metadata for the given node.
pub fn node_metadata(&self, _id:ast::ID) -> FallibleResult<NodeMetadata> {
// todo!()
#[derive(Clone,Copy,Debug,Display,Fail)]
struct NotImplemented;
Err(NotImplemented.into())
}
/// Update metadata for the given node.
pub fn update_node_metadata<F>(&self, _id:ast::ID, _updater:F) -> FallibleResult<NodeMetadata>
where F : FnOnce(&mut NodeMetadata) {
todo!()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::double_representation::definition::DefinitionName;
use crate::executor::test_utils::TestWithLocalPoolExecutor;
use crate::controller::module;
use crate::controller::notification;
use ast::HasRepr;
use data::text::Index;
use data::text::TextChange;
use json_rpc::test_util::transport::mock::MockTransport;
use parser::Parser;
use utils::test::ExpectTuple;
use wasm_bindgen_test::wasm_bindgen_test;
struct GraphControllerFixture(TestWithLocalPoolExecutor);
impl GraphControllerFixture {
pub fn set_up() -> GraphControllerFixture {
let nested = TestWithLocalPoolExecutor::set_up();
Self(nested)
}
pub fn run_graph_for_program<Test,Fut>(&mut self, code:impl Str, function_name:impl Str, test:Test)
where Test : FnOnce(module::Handle,Handle) -> Fut + 'static,
Fut : Future<Output=()> {
let fm = file_manager_client::Handle::new(MockTransport::new());
let loc = controller::module::Location("Main".to_string());
let parser = Parser::new_or_panic();
let module = controller::module::Handle::new_mock(loc,code.as_ref(),default(),fm,parser).unwrap();
let graph_id = Id::new_single_crumb(DefinitionName::new_plain(function_name.into()));
let graph = module.get_graph_controller(graph_id).unwrap();
self.0.run_test(async move {
test(module,graph).await
})
}
pub fn run_inline_graph<Test,Fut>(&mut self, definition_body:impl Str, test:Test)
where Test : FnOnce(module::Handle,Handle) -> Fut + 'static,
Fut : Future<Output=()> {
assert_eq!(definition_body.as_ref().contains('\n'), false);
let code = format!("main = {}", definition_body.as_ref());
let name = "main";
self.run_graph_for_program(code,name,test)
}
}
#[wasm_bindgen_test]
fn graph_controller_notification_relay() {
let mut test = GraphControllerFixture::set_up();
test.run_graph_for_program("main = 2 + 2", "main", |module,graph| async move {
let text_change = TextChange::insert(Index::new(12), "2".into());
module.apply_code_change(&text_change).unwrap();
let mut sub = graph.subscribe();
module.apply_code_change(&TextChange::insert(Index::new(1),"2".to_string())).unwrap();
assert_eq!(Some(notification::Graph::Invalidate), sub.next().await);
})
}
#[wasm_bindgen_test]
fn graph_controller_inline_definition() {
let mut test = GraphControllerFixture::set_up();
const EXPRESSION: &str = "2+2";
test.run_inline_graph(EXPRESSION, |_,graph| async move {
let nodes = graph.nodes().unwrap();
let (node,) = nodes.expect_tuple();
assert_eq!(node.info.expression().repr(), EXPRESSION);
let id = node.info.id();
let node = graph.node(id).unwrap();
assert_eq!(node.info.expression().repr(), EXPRESSION);
})
}
#[wasm_bindgen_test]
fn graph_controller_block_definition() {
let mut test = GraphControllerFixture::set_up();
let program = r"
main =
foo = 2
print foo";
test.run_graph_for_program(program, "main", |_,graph| async move {
let nodes = graph.nodes().unwrap();
let (node1,node2) = nodes.expect_tuple();
assert_eq!(node1.info.expression().repr(), "2");
assert_eq!(node2.info.expression().repr(), "print foo");
})
}
}

View File

@ -7,9 +7,9 @@
use crate::prelude::*; use crate::prelude::*;
use crate::controller::FallibleResult;
use crate::controller::notification; use crate::controller::notification;
use crate::double_representation::text::apply_code_change_to_id_map; use crate::double_representation::text::apply_code_change_to_id_map;
use crate::double_representation::definition::DefinitionInfo;
use crate::executor::global::spawn; use crate::executor::global::spawn;
use parser::api::SourceFile; use parser::api::SourceFile;
@ -17,7 +17,9 @@ use ast;
use ast::Ast; use ast::Ast;
use ast::HasRepr; use ast::HasRepr;
use ast::IdMap; use ast::IdMap;
use ast::known;
use data::text::*; use data::text::*;
use double_representation as dr;
use file_manager_client as fmc; use file_manager_client as fmc;
use flo_stream::MessagePublisher; use flo_stream::MessagePublisher;
use flo_stream::Subscriber; use flo_stream::Subscriber;
@ -108,7 +110,7 @@ shared! { Handle
/// Publisher of "text changed" notifications /// Publisher of "text changed" notifications
text_notifications : notification::Publisher<notification::Text>, text_notifications : notification::Publisher<notification::Text>,
/// Publisher of "graph changed" notifications /// Publisher of "graph changed" notifications
graph_notifications : notification::Publisher<notification::Graph>, graph_notifications : notification::Publisher<notification::Graphs>,
/// The logger handle. /// The logger handle.
logger: Logger, logger: Logger,
} }
@ -140,6 +142,12 @@ shared! { Handle
self.module.ast.repr() self.module.ast.repr()
} }
/// Obtains definition information for given graph id.
pub fn find_definition(&self,id:&dr::graph::Id) -> FallibleResult<DefinitionInfo> {
let module = known::Module::try_new(self.module.ast.clone())?;
double_representation::graph::traverse_for_definition(module,id)
}
/// Check if current module state is synchronized with given code. If it's not, log error, /// Check if current module state is synchronized with given code. If it's not, log error,
/// and update module state to match the `code` passed as argument. /// and update module state to match the `code` passed as argument.
pub fn check_code_sync(&mut self, code:String) -> FallibleResult<()> { pub fn check_code_sync(&mut self, code:String) -> FallibleResult<()> {
@ -159,7 +167,7 @@ shared! { Handle
} }
/// Get subscriber receiving notifications about changes in module's graph representation. /// Get subscriber receiving notifications about changes in module's graph representation.
pub fn subscribe_graph_notifications(&mut self) -> Subscriber<notification::Graph> { pub fn subscribe_graph_notifications(&mut self) -> Subscriber<notification::Graphs> {
self.graph_notifications.subscribe() self.graph_notifications.subscribe()
} }
} }
@ -178,8 +186,9 @@ impl Handle {
let text_notifications = default(); let text_notifications = default();
let graph_notifications = default(); let graph_notifications = default();
let data = Controller {location,module,file_manager,parser,id_map,logger,text_notifications,
graph_notifications}; let data = Controller {location,module,file_manager,parser,id_map,logger,
text_notifications,graph_notifications};
let handle = Handle::new_from_data(data); let handle = Handle::new_from_data(data);
handle.load_file().await?; handle.load_file().await?;
Ok(handle) Ok(handle)
@ -216,6 +225,13 @@ impl Handle {
async move { fm.write(path.clone(),code?).await } async move { fm.write(path.clone(),code?).await }
} }
/// Returns a graph controller for graph in this module's subtree identified by `id`.
/// Reuses already existing controller if possible.
pub fn get_graph_controller(&self, id:dr::graph::Id)
-> FallibleResult<controller::graph::Handle> {
controller::graph::Handle::new(self.clone(),id)
}
#[cfg(test)] #[cfg(test)]
pub fn new_mock pub fn new_mock
( location : Location ( location : Location
@ -240,7 +256,7 @@ impl Controller {
fn update_ast(&mut self,ast:Ast) { fn update_ast(&mut self,ast:Ast) {
self.module.ast = ast; self.module.ast = ast;
let text_change = notification::Text::Invalidate; let text_change = notification::Text::Invalidate;
let graph_change = notification::Graph::Invalidate; let graph_change = notification::Graphs::Invalidate;
let code_notify = self.text_notifications.publish(text_change); let code_notify = self.text_notifications.publish(text_change);
let graph_notify = self.graph_notifications.publish(graph_change); let graph_notify = self.graph_notifications.publish(graph_change);
spawn(async move { futures::join!(code_notify,graph_notify); }); spawn(async move { futures::join!(code_notify,graph_notify); });
@ -248,6 +264,10 @@ impl Controller {
} }
// =============
// === Tests ===
// =============
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
@ -318,7 +338,7 @@ mod test {
// Check emitted notifications // Check emitted notifications
assert_eq!(Some(notification::Text::Invalidate ), text_notifications.next().await ); assert_eq!(Some(notification::Text::Invalidate ), text_notifications.next().await );
assert_eq!(Some(notification::Graph::Invalidate), graph_notifications.next().await); assert_eq!(Some(notification::Graphs::Invalidate), graph_notifications.next().await);
}); });
} }
} }

View File

@ -16,7 +16,6 @@ use crate::prelude::*;
/// therefore there is no need for setting big buffers. /// therefore there is no need for setting big buffers.
const NOTIFICATION_BUFFER_SIZE : usize = 36; const NOTIFICATION_BUFFER_SIZE : usize = 36;
/// A notification publisher which implements Debug and Default. /// A notification publisher which implements Debug and Default.
#[derive(Shrinkwrap)] #[derive(Shrinkwrap)]
#[shrinkwrap(mutable)] #[shrinkwrap(mutable)]
@ -36,7 +35,6 @@ impl<Message:'static> Debug for Publisher<Message> {
// ===================================== // =====================================
// === Double Representation Changes === // === Double Representation Changes ===
// ===================================== // =====================================
@ -51,11 +49,33 @@ pub enum Text {
} }
// === Graph === // === Graphs ===
/// A notification about changes of graph representation of a module. /// A notification about changes of graph representation of a module.
#[derive(Copy,Clone,Debug,Eq,PartialEq)] #[derive(Copy,Clone,Debug,Eq,PartialEq)]
pub enum Graphs {
/// The content should be fully reloaded.
Invalidate,
}
// === Graph ===
/// A notification about changes of a specific graph in a module.
#[derive(Copy,Clone,Debug,Eq,PartialEq)]
pub enum Graph { pub enum Graph {
/// The content should be fully reloaded. /// The content should be fully reloaded.
Invalidate, Invalidate,
} }
// === Node ===
/// A notification about changes of specific node in a graph.
#[derive(Copy,Clone,Debug,Eq,PartialEq)]
pub enum Node {
/// The content should be fully reloaded.
Invalidate,
}

View File

@ -5,8 +5,6 @@
use crate::prelude::*; use crate::prelude::*;
use crate::controller::FallibleResult;
use file_manager_client as fmc; use file_manager_client as fmc;
use json_rpc::Transport; use json_rpc::Transport;
use parser::Parser; use parser::Parser;

View File

@ -5,7 +5,6 @@
//! user. //! user.
use crate::prelude::*; use crate::prelude::*;
use crate::controller::FallibleResult;
use crate::controller::notification; use crate::controller::notification;
use crate::executor::global::spawn; use crate::executor::global::spawn;

View File

@ -8,7 +8,7 @@ use ast::Shape;
use ast::known; use ast::known;
use ast::prefix; use ast::prefix;
use ast::opr; use ast::opr;
use shapely::EmptyIterator;
// ================= // =================
@ -93,16 +93,16 @@ impl DefinitionName {
} }
} }
impl ToString for DefinitionName { impl Display for DefinitionName {
fn to_string(&self) -> String { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut pieces = self.extended_target.iter().map(|s| s.as_str()).collect_vec(); let mut pieces = self.extended_target.iter().map(|s| s.as_str()).collect_vec();
pieces.push(&self.name); pieces.push(&self.name);
pieces.join(opr::predefined::ACCESS) let text = pieces.join(opr::predefined::ACCESS);
write!(f, "{}", text)
} }
} }
// ====================== // ======================
// === DefinitionInfo === // === DefinitionInfo ===
// ====================== // ======================
@ -216,6 +216,16 @@ impl DefinitionProvider for known::Block {
} }
} }
impl DefinitionProvider for DefinitionInfo {
fn scope_kind() -> ScopeKind { ScopeKind::NonRoot }
fn line_asts<'a>(&'a self) -> Box<dyn Iterator<Item=&'a Ast> + 'a> {
match self.ast.rarg.shape() {
ast::Shape::Block(_) => self.ast.rarg.iter(),
_ => Box::new(EmptyIterator::new())
}
}
}
// ============= // =============

View File

@ -3,6 +3,9 @@
use crate::prelude::*; use crate::prelude::*;
use crate::double_representation::definition; use crate::double_representation::definition;
use crate::double_representation::definition::DefinitionInfo;
use crate::double_representation::definition::DefinitionName;
use crate::double_representation::definition::DefinitionProvider;
use crate::double_representation::node; use crate::double_representation::node;
use ast::Ast; use ast::Ast;
@ -10,6 +13,59 @@ use ast::known;
// ================
// === Graph Id ===
// ================
/// Crumb describes step that needs to be done when going from context (for graph being a module)
/// to the target.
// TODO [mwu]
// Currently we support only entering named definitions.
pub type Crumb = DefinitionName;
/// Identifies graph in the module.
#[derive(Clone,Debug,Eq,Hash,PartialEq)]
pub struct Id {
/// Sequence of traverses from module root up to the identified graph.
pub crumbs : Vec<Crumb>,
}
impl Id {
/// Creates a new graph identifier consisting of a single crumb.
pub fn new_single_crumb(crumb:DefinitionName) -> Id {
let crumbs = vec![crumb];
Id {crumbs}
}
}
// ===============================
// === Finding Graph In Module ===
// ===============================
#[derive(Fail,Clone,Debug)]
#[fail(display="Definition ID was empty")]
struct CannotFindDefinition(Id);
#[derive(Fail,Clone,Debug)]
#[fail(display="Definition ID was empty")]
struct EmptyDefinitionId;
/// Looks up graph in the module.
pub fn traverse_for_definition
(ast:ast::known::Module, id:&Id) -> FallibleResult<DefinitionInfo> {
let err = || CannotFindDefinition(id.clone());
let mut crumb_iter = id.crumbs.iter();
let first_crumb = crumb_iter.next().ok_or(EmptyDefinitionId)?;
let mut definition = ast.find_definition(first_crumb).ok_or_else(err)?;
for crumb in crumb_iter {
definition = definition.find_definition(crumb).ok_or_else(err)?;
}
Ok(definition)
}
// ================= // =================
// === GraphInfo === // === GraphInfo ===
// ================= // =================
@ -17,20 +73,17 @@ use ast::known;
/// Description of the graph, based on information available in AST. /// Description of the graph, based on information available in AST.
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
pub struct GraphInfo { pub struct GraphInfo {
name : definition::DefinitionName, source : DefinitionInfo,
args : Vec<Ast>,
/// Describes all known nodes in this graph (does not include special pseudo-nodes like graph /// Describes all known nodes in this graph (does not include special pseudo-nodes like graph
/// inputs and outputs). /// inputs and outputs).
pub nodes:Vec<node::NodeInfo>, pub nodes : Vec<node::NodeInfo>,
} }
impl GraphInfo { impl GraphInfo {
/// Describe graph of the given definition. /// Describe graph of the given definition.
pub fn from_definition(info:&definition::DefinitionInfo) -> GraphInfo { pub fn from_definition(source:DefinitionInfo) -> GraphInfo {
let name = info.name.clone(); let nodes = Self::from_function_binding(source.ast.clone());
let args = info.args.clone(); GraphInfo {source,nodes}
let nodes = Self::from_function_binding(info.ast.clone());
GraphInfo {name,args,nodes}
} }
/// Lists nodes in the given binding's ast (infix expression). /// Lists nodes in the given binding's ast (infix expression).
@ -79,6 +132,7 @@ mod tests {
use crate::double_representation::definition::DefinitionName; use crate::double_representation::definition::DefinitionName;
use crate::double_representation::definition::DefinitionProvider; use crate::double_representation::definition::DefinitionProvider;
use ast::HasRepr;
use parser::api::IsParser; use parser::api::IsParser;
use wasm_bindgen_test::wasm_bindgen_test; use wasm_bindgen_test::wasm_bindgen_test;
@ -89,8 +143,7 @@ mod tests {
let module = parser.parse_module(program.into(), default()).unwrap(); let module = parser.parse_module(program.into(), default()).unwrap();
let name = DefinitionName::new_plain("main"); let name = DefinitionName::new_plain("main");
let main = module.find_definition(&name).unwrap(); let main = module.find_definition(&name).unwrap();
println!("{:?}",module); GraphInfo::from_definition(main)
GraphInfo::from_definition(&main)
} }
#[wasm_bindgen_test] #[wasm_bindgen_test]
@ -107,7 +160,7 @@ mod tests {
let graph = main_graph(&mut parser, program); let graph = main_graph(&mut parser, program);
assert_eq!(graph.nodes.len(), 1); assert_eq!(graph.nodes.len(), 1);
let node = &graph.nodes[0]; let node = &graph.nodes[0];
assert_eq!(node.expression_text(), "2+2"); assert_eq!(node.expression().repr(), "2+2");
let _ = node.id(); // just to make sure it is available let _ = node.id(); // just to make sure it is available
} }
} }
@ -119,15 +172,13 @@ mod tests {
main = main =
foo = node foo = node
foo a = not_node foo a = not_node
Int.= a = node
node node
"; ";
// TODO [mwu]
// Add case like `Int.= a = node` once https://github.com/luna/enso/issues/565 is fixed
let graph = main_graph(&mut parser, program); let graph = main_graph(&mut parser, program);
assert_eq!(graph.nodes.len(), 2); assert_eq!(graph.nodes.len(), 2);
for node in graph.nodes.iter() { for node in graph.nodes.iter() {
assert_eq!(node.expression_text(), "node"); assert_eq!(node.expression().repr(), "node");
} }
} }
} }

View File

@ -1,7 +1,6 @@
//! Code for node discovery and other node-related tasks. //! Code for node discovery and other node-related tasks.
use ast::Ast; use ast::Ast;
use ast::HasRepr;
use ast::ID; use ast::ID;
use ast::known; use ast::known;
@ -47,23 +46,18 @@ impl NodeInfo {
/// Node's unique ID. /// Node's unique ID.
pub fn id(&self) -> ID { pub fn id(&self) -> ID {
// Panic must not happen, as the only available constructor checks that // Panic must not happen, as the only available constructors checks that
// there is an ID present. // there is an ID present.
self.expression_ast().id.expect("Node AST must bear an ID") self.expression().id.expect("Node AST must bear an ID")
} }
/// AST of the node's expression. /// AST of the node's expression.
pub fn expression_ast(&self) -> &Ast { pub fn expression(&self) -> &Ast {
match self { match self {
NodeInfo::Binding {infix} => &infix.rarg, NodeInfo::Binding {infix} => &infix.rarg,
NodeInfo::Expression{ast} => &ast, NodeInfo::Expression{ast} => &ast,
} }
} }
/// The node's expression textual representation.
pub fn expression_text(&self) -> String {
self.expression_ast().repr()
}
} }
@ -76,13 +70,14 @@ impl NodeInfo {
mod tests { mod tests {
use super::*; use super::*;
use ast::HasRepr;
use wasm_bindgen_test::wasm_bindgen_test; use wasm_bindgen_test::wasm_bindgen_test;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
fn expect_node(ast:Ast, expression_text:&str, id:ID) { fn expect_node(ast:Ast, expression_text:&str, id:ID) {
let node_info = NodeInfo::from_line_ast(&ast).expect("expected a node"); let node_info = NodeInfo::from_line_ast(&ast).expect("expected a node");
assert_eq!(node_info.expression_text(),expression_text); assert_eq!(node_info.expression().repr(),expression_text);
assert_eq!(node_info.id(), id); assert_eq!(node_info.id(), id);
} }

View File

@ -35,6 +35,8 @@ pub mod prelude {
pub use futures::Stream; pub use futures::Stream;
pub use futures::StreamExt; pub use futures::StreamExt;
pub use futures::task::LocalSpawnExt; pub use futures::task::LocalSpawnExt;
pub use utils::fail::FallibleResult;
} }
use crate::prelude::*; use crate::prelude::*;

View File

@ -4,7 +4,6 @@
use crate::prelude::*; use crate::prelude::*;
use crate::view::layout::ViewLayout; use crate::view::layout::ViewLayout;
use crate::controller::FallibleResult;
use ensogl::control::callback::CallbackHandle; use ensogl::control::callback::CallbackHandle;
use ensogl::control::io::keyboard::listener::KeyboardFrpBindings; use ensogl::control::io::keyboard::listener::KeyboardFrpBindings;

View File

@ -0,0 +1,4 @@
//! General-purpose code related to error handling.
/// General-purpose `Result` supporting any `Error`-compatible failures.
pub type FallibleResult<T> = Result<T,failure::Error>;

View File

@ -14,4 +14,5 @@ pub use enso_prelude as prelude;
pub mod channel; pub mod channel;
pub mod env; pub mod env;
pub mod fail;
pub mod test; pub mod test;