mirror of
https://github.com/enso-org/enso.git
synced 2024-12-19 15:12:26 +03:00
Connection Discovery (https://github.com/enso-org/ide/pull/380)
ref #2203
Original commit: c1c68bf6a0
This commit is contained in:
parent
5a8d1a9c05
commit
6f3a328c23
@ -80,6 +80,14 @@ struct MismatchedCrumbType;
|
||||
/// Sequence of `Crumb`s describing traversal path through AST.
|
||||
pub type Crumbs = Vec<Crumb>;
|
||||
|
||||
/// Helper macro. Behaves like `vec!` but converts each element into `Crumb`.
|
||||
#[macro_export]
|
||||
macro_rules! crumbs {
|
||||
( $( $x:expr ),* ) => {
|
||||
vec![$(Crumb::from($x)),*]
|
||||
};
|
||||
}
|
||||
|
||||
/// Crumb identifies location of child AST in an AST node. Allows for a single step AST traversal.
|
||||
/// The enum variants are paired with Shape variants. For example, `ModuleCrumb` allows obtaining
|
||||
/// (or setting) `Ast` stored within a `Module` shape.
|
||||
@ -94,21 +102,21 @@ pub type Crumbs = Vec<Crumb>;
|
||||
// === InvalidSuffix ===
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,PartialOrd,Ord,Hash)]
|
||||
pub struct InvalidSuffixCrumb;
|
||||
|
||||
|
||||
// === TextLineFmt ===
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,PartialOrd,Ord,Hash)]
|
||||
pub struct TextLineFmtCrumb {pub segment_index:usize}
|
||||
|
||||
|
||||
// === TextBlockFmt ===
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,PartialOrd,Ord,Hash)]
|
||||
pub struct TextBlockFmtCrumb {
|
||||
pub text_line_index : usize,
|
||||
pub segment_index : usize
|
||||
@ -118,7 +126,7 @@ pub struct TextBlockFmtCrumb {
|
||||
// === TextUnclosed ===
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,PartialOrd,Ord,Hash)]
|
||||
pub struct TextUnclosedCrumb {
|
||||
pub text_line_crumb : TextLineFmtCrumb
|
||||
}
|
||||
@ -127,7 +135,7 @@ pub struct TextUnclosedCrumb {
|
||||
// === Prefix ===
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,PartialOrd,Ord,Hash)]
|
||||
pub enum PrefixCrumb {
|
||||
Func,
|
||||
Arg
|
||||
@ -137,7 +145,7 @@ pub enum PrefixCrumb {
|
||||
// === Infix ===
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,PartialOrd,Ord,Hash)]
|
||||
pub enum InfixCrumb {
|
||||
LeftOperand,
|
||||
Operator,
|
||||
@ -148,7 +156,7 @@ pub enum InfixCrumb {
|
||||
// === SectionLeft ===
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,PartialOrd,Ord,Hash)]
|
||||
pub enum SectionLeftCrumb {
|
||||
Arg,
|
||||
Opr
|
||||
@ -158,7 +166,7 @@ pub enum SectionLeftCrumb {
|
||||
// === SectionRight ===
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,PartialOrd,Ord,Hash)]
|
||||
pub enum SectionRightCrumb {
|
||||
Opr,
|
||||
Arg
|
||||
@ -168,21 +176,21 @@ pub enum SectionRightCrumb {
|
||||
// === SectionSides ===
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,PartialOrd,Ord,Hash)]
|
||||
pub struct SectionSidesCrumb;
|
||||
|
||||
|
||||
// === Module ===
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,PartialOrd,Ord,Hash)]
|
||||
pub struct ModuleCrumb {pub line_index:usize}
|
||||
|
||||
|
||||
// === Block ===
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,PartialOrd,Ord,Hash)]
|
||||
pub enum BlockCrumb {
|
||||
/// The first non-empty line in block.
|
||||
HeadLine,
|
||||
@ -194,14 +202,14 @@ pub enum BlockCrumb {
|
||||
// === Import ===
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,PartialOrd,Ord,Hash)]
|
||||
pub struct ImportCrumb {pub index:usize}
|
||||
|
||||
|
||||
// === Mixfix ===
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,PartialOrd,Ord,Hash)]
|
||||
pub enum MixfixCrumb {
|
||||
Name {index:usize},
|
||||
Args {index:usize}
|
||||
@ -211,14 +219,14 @@ pub enum MixfixCrumb {
|
||||
// === Group ===
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,PartialOrd,Ord,Hash)]
|
||||
pub struct GroupCrumb;
|
||||
|
||||
|
||||
// === Def ===
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,PartialOrd,Ord,Hash)]
|
||||
pub enum DefCrumb {
|
||||
Name,
|
||||
Args {index:usize},
|
||||
@ -281,7 +289,7 @@ macro_rules! impl_crumbs {
|
||||
}
|
||||
|
||||
/// Crumb identifies location of child AST in an AST node. Allows for a single step AST traversal.
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,Hash)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Eq,PartialOrd,Ord,Hash)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Crumb {
|
||||
$($id($crumb_id),)*
|
||||
@ -879,7 +887,7 @@ pub fn non_empty_line_indices<'a, T:'a>
|
||||
// ===============
|
||||
|
||||
/// Item which location is identified by `Crumbs`.
|
||||
#[derive(Clone,Debug,Shrinkwrap,PartialEq,Eq,Hash)]
|
||||
#[derive(Clone,Debug,Shrinkwrap,PartialEq,Eq,PartialOrd,Ord,Hash)]
|
||||
pub struct Located<T> {
|
||||
/// Crumbs from containing parent.
|
||||
pub crumbs : Crumbs,
|
||||
|
@ -32,7 +32,7 @@ pub struct Handle {
|
||||
impl Handle {
|
||||
/// Create a new project controller.
|
||||
///
|
||||
/// The remote connections should be already established.
|
||||
/// The remote connection should be already established.
|
||||
pub fn new(file_manager_transport:impl Transport + 'static) -> Self {
|
||||
Handle {
|
||||
file_manager : fmc::Handle::new(file_manager_transport),
|
||||
|
@ -2,11 +2,14 @@
|
||||
//! module.
|
||||
|
||||
pub mod alias_analysis;
|
||||
pub mod connection;
|
||||
pub mod definition;
|
||||
pub mod graph;
|
||||
pub mod node;
|
||||
pub mod text;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test_utils;
|
||||
|
||||
|
||||
// ==============
|
||||
|
@ -119,7 +119,7 @@ impl Scope {
|
||||
#[derive(Clone,Debug,Default)]
|
||||
pub struct AliasAnalyzer {
|
||||
/// Root scope for this analyzer.
|
||||
root_scope : Scope,
|
||||
pub root_scope : Scope,
|
||||
/// Stack of scopes that shadow the root one.
|
||||
shadowing_scopes : Vec<Scope>,
|
||||
/// Stack of context. Lack of any context information is considered non-pattern context.
|
||||
@ -229,7 +229,7 @@ impl AliasAnalyzer {
|
||||
}
|
||||
|
||||
/// Processes all subtrees of the given AST in their respective locations.
|
||||
fn process_subtrees(&mut self, ast:&Ast) {
|
||||
pub fn process_subtrees(&mut self, ast:&impl Crumbable) {
|
||||
for (crumb,ast) in ast.enumerate() {
|
||||
self.process_subtree_at(crumb, ast)
|
||||
}
|
||||
@ -241,7 +241,7 @@ impl AliasAnalyzer {
|
||||
pub fn process_ast(&mut self, ast:&Ast) {
|
||||
if let Some(definition) = DefinitionInfo::from_line_ast(&ast,ScopeKind::NonRoot,default()) {
|
||||
// If AST looks like definition, we disregard its arguments and body, as they cannot
|
||||
// form connections in the analyzed graph. However, we need to record the name, because
|
||||
// form connection in the analyzed graph. However, we need to record the name, because
|
||||
// it may shadow identifier from parent scope.
|
||||
let name = NormalizedName::new(definition.name.name);
|
||||
self.record_identifier(OccurrenceKind::Introduced,name);
|
||||
@ -320,12 +320,19 @@ impl AliasAnalyzer {
|
||||
|
||||
/// Describes identifiers that nodes introduces into the graph and identifiers from graph's scope
|
||||
/// that node uses. This logic serves as a base for connection discovery.
|
||||
pub fn analyse_identifier_usage(node:&NodeInfo) -> IdentifierUsage {
|
||||
pub fn analyse_node(node:&NodeInfo) -> IdentifierUsage {
|
||||
let mut analyzer = AliasAnalyzer::new();
|
||||
analyzer.process_ast(node.ast());
|
||||
analyzer.root_scope.symbols
|
||||
}
|
||||
|
||||
/// Describes variable usage within a given code block.
|
||||
pub fn analyse_block(block:&ast::Block<Ast>) -> IdentifierUsage {
|
||||
let mut analyzer = AliasAnalyzer::default();
|
||||
analyzer.process_subtrees(block);
|
||||
analyzer.root_scope.symbols
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
@ -353,7 +360,7 @@ mod tests {
|
||||
println!("Case: {}",&case.code);
|
||||
let ast = parser.parse_line(&case.code).unwrap();
|
||||
let node = NodeInfo::from_line_ast(&ast).unwrap();
|
||||
let result = analyse_identifier_usage(&node);
|
||||
let result = analyse_node(&node);
|
||||
println!("Analysis results: {:?}", result);
|
||||
validate_identifiers("introduced",&node, case.expected_introduced, &result.introduced);
|
||||
validate_identifiers("used", &node, case.expected_used, &result.used);
|
||||
|
@ -5,9 +5,9 @@ use crate::prelude::*;
|
||||
use crate::double_representation::alias_analysis::NormalizedName;
|
||||
use crate::double_representation::alias_analysis::LocatedName;
|
||||
use crate::double_representation::node::NodeInfo;
|
||||
use crate::double_representation::test_utils::MarkdownProcessor;
|
||||
|
||||
use regex::Captures;
|
||||
use regex::Match;
|
||||
use regex::Regex;
|
||||
use regex::Replacer;
|
||||
|
||||
@ -75,30 +75,11 @@ const USED:&str="used";
|
||||
/// * accumulates spans of introduced and used identifiers.
|
||||
#[derive(Debug,Default)]
|
||||
struct MarkdownReplacer {
|
||||
markdown_bytes_consumed : usize,
|
||||
processor:MarkdownProcessor,
|
||||
/// Spans in the unmarked code.
|
||||
introduced : Vec<Range<usize>>,
|
||||
introduced:Vec<Range<usize>>,
|
||||
/// Spans in the unmarked code.
|
||||
used : Vec<Range<usize>>,
|
||||
}
|
||||
|
||||
impl MarkdownReplacer {
|
||||
fn marked_to_unmarked_index(&self, i:usize) -> usize {
|
||||
assert!(self.markdown_bytes_consumed <= i);
|
||||
i - self.markdown_bytes_consumed
|
||||
}
|
||||
/// Increments the consumed marker bytes count by size of a single marker character.
|
||||
fn consume_marker(&mut self) {
|
||||
self.markdown_bytes_consumed += '«'.len_utf8();
|
||||
}
|
||||
/// Consumes opening and closing marker. Returns span of marked item in unmarked text indices.
|
||||
fn consume_marked(&mut self, capture:&Match) -> Range<usize> {
|
||||
self.consume_marker();
|
||||
let start = self.marked_to_unmarked_index(capture.start());
|
||||
let end = self.marked_to_unmarked_index(capture.end());
|
||||
self.consume_marker();
|
||||
start .. end
|
||||
}
|
||||
used:Vec<Range<usize>>,
|
||||
}
|
||||
|
||||
// Processes every single match for a marked entity.
|
||||
@ -112,13 +93,11 @@ impl Replacer for MarkdownReplacer {
|
||||
panic!("Unexpected capture: expected named capture `{}` or `{}`.",INTRODUCED,USED)
|
||||
};
|
||||
|
||||
let span = self.consume_marked(&matched);
|
||||
let out_vec = match kind {
|
||||
Kind::Introduced => &mut self.introduced,
|
||||
Kind::Used => &mut self.used,
|
||||
let span = self.processor.process_match(captures,&matched,dst);
|
||||
match kind {
|
||||
Kind::Introduced => self.introduced.push(span),
|
||||
Kind::Used => self.used.push(span),
|
||||
};
|
||||
out_vec.push(span);
|
||||
dst.push_str(matched.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
209
gui/src/rust/ide/src/double_representation/connection.rs
Normal file
209
gui/src/rust/ide/src/double_representation/connection.rs
Normal file
@ -0,0 +1,209 @@
|
||||
//! Code related to connection discovery and operations.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::double_representation::alias_analysis::analyse_block;
|
||||
use crate::double_representation::alias_analysis::NormalizedName;
|
||||
use crate::double_representation::node::Id;
|
||||
use crate::double_representation::node::NodeInfo;
|
||||
|
||||
use ast::crumbs::Crumb;
|
||||
use ast::crumbs::Crumbs;
|
||||
use crate::double_representation::definition::{DefinitionInfo, ScopeKind};
|
||||
|
||||
|
||||
// ================
|
||||
// === Endpoint ===
|
||||
// ================
|
||||
|
||||
/// A connection endpoint.
|
||||
#[derive(Clone,Debug,PartialEq)]
|
||||
pub struct Endpoint {
|
||||
/// Id of the node where the endpoint is located.
|
||||
pub node : Id,
|
||||
/// Crumbs to the AST creating this endpoint. These crumbs are relative to the node's AST,
|
||||
/// not just expression, if the node is binding, there'll crumb for left/right operand first.
|
||||
pub crumbs : Crumbs,
|
||||
}
|
||||
|
||||
impl Endpoint {
|
||||
/// First crumb identifies line in a given block, i.e. the node. Remaining crumbs identify
|
||||
/// AST within the node's AST.
|
||||
///
|
||||
/// Returns None if first crumb is not present or does not denote a valid node.
|
||||
fn new_in_block(block:&ast::Block<Ast>, mut crumbs:Crumbs) -> Option<Endpoint> {
|
||||
let line_crumb = crumbs.pop_front()?;
|
||||
let line_crumb = match line_crumb {
|
||||
Crumb::Block(block_crumb) => Some(block_crumb),
|
||||
_ => None,
|
||||
}?;
|
||||
let line_ast = block.get(&line_crumb).ok()?;
|
||||
let definition = DefinitionInfo::from_line_ast(&line_ast,ScopeKind::NonRoot,block.indent);
|
||||
let is_non_def = definition.is_none();
|
||||
let node = is_non_def.and_option_from(|| NodeInfo::from_line_ast(&line_ast))?.id();
|
||||
Some(Endpoint { node, crumbs })
|
||||
}
|
||||
}
|
||||
|
||||
/// Connection source, i.e. the port generating the data / identifier introducer.
|
||||
pub type Source = Endpoint;
|
||||
|
||||
/// Connection destination, i.e. the port receiving data / identifier user.
|
||||
pub type Destination = Endpoint;
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// === Connection ===
|
||||
// ==================
|
||||
|
||||
/// Describes a connection between two endpoints: from `source` to `destination`.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Debug,PartialEq)]
|
||||
pub struct Connection {
|
||||
pub source:Source,
|
||||
pub destination:Destination,
|
||||
}
|
||||
|
||||
/// Lists all the connection in the graph for the given code block.
|
||||
pub fn list_block(block:&ast::Block<Ast>) -> Vec<Connection> {
|
||||
let identifiers = analyse_block(block);
|
||||
let introduced_iter = identifiers.introduced.into_iter();
|
||||
type NameMap = HashMap<NormalizedName,Endpoint>;
|
||||
let introduced_names = introduced_iter.flat_map(|name| {
|
||||
let endpoint = Endpoint::new_in_block(block,name.crumbs)?;
|
||||
Some((name.item,endpoint))
|
||||
}).collect::<NameMap>();
|
||||
identifiers.used.into_iter().flat_map(|name| {
|
||||
// If name is both introduced and used in the graph's scope; and both of these occurrences
|
||||
// can be represented as endpoints, then we have a connection.
|
||||
let source = introduced_names.get(&name).cloned()?;
|
||||
let destination = Endpoint::new_in_block(block,name.crumbs)?;
|
||||
Some(Connection {source,destination})
|
||||
}).collect()
|
||||
}
|
||||
|
||||
/// Lists all the connection in the single-expression definition body.
|
||||
pub fn list_expression(_ast:&Ast) -> Vec<Connection> {
|
||||
// At this points single-expression graphs do not have any connection.
|
||||
// This will change when there will be input/output pseudo-nodes.
|
||||
vec![]
|
||||
}
|
||||
|
||||
/// Lists connections in the given definition body. For now it only makes sense for block shape.
|
||||
pub fn list(body:&Ast) -> Vec<Connection> {
|
||||
match body.shape() {
|
||||
ast::Shape::Block(block) => list_block(block),
|
||||
_ => list_expression(body),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Tests ===
|
||||
// =============
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use parser::Parser;
|
||||
use crate::double_representation::definition::DefinitionInfo;
|
||||
use crate::double_representation::graph::GraphInfo;
|
||||
use ast::crumbs;
|
||||
use ast::crumbs::Crumb;
|
||||
use ast::crumbs::InfixCrumb;
|
||||
|
||||
struct TestRun {
|
||||
graph : GraphInfo,
|
||||
connections : Vec<Connection>
|
||||
}
|
||||
|
||||
impl TestRun {
|
||||
fn from_definition(definition:DefinitionInfo) -> TestRun {
|
||||
let graph = GraphInfo::from_definition(definition.clone());
|
||||
let repr_of = |connection:&Connection| {
|
||||
let endpoint = &connection.source;
|
||||
let node = graph.find_node(endpoint.node).unwrap();
|
||||
let ast = node.ast().get_traversing(&endpoint.crumbs).unwrap();
|
||||
ast.repr()
|
||||
};
|
||||
|
||||
let mut connections = graph.connections();
|
||||
connections.sort_by(|lhs,rhs| {
|
||||
repr_of(&lhs).cmp(&repr_of(&rhs))
|
||||
});
|
||||
|
||||
TestRun {graph,connections}
|
||||
}
|
||||
|
||||
fn from_main_def(code:impl Str) -> TestRun {
|
||||
let parser = Parser::new_or_panic();
|
||||
let module = parser.parse_module(code,default()).unwrap();
|
||||
let definition = DefinitionInfo::from_root_line(&module.lines[0]).unwrap();
|
||||
Self::from_definition(definition)
|
||||
}
|
||||
|
||||
fn from_block(code:impl Str) -> TestRun {
|
||||
let body = code.as_ref().lines().map(|line| format!(" {}", line.trim())).join("\n");
|
||||
let definition_code = format!("main =\n{}",body);
|
||||
Self::from_main_def(definition_code)
|
||||
}
|
||||
|
||||
fn endpoint_node_repr(&self, endpoint:&Endpoint) -> String {
|
||||
self.graph.find_node(endpoint.node).unwrap().ast().clone().repr()
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
pub fn connection_listing_test_plain() {
|
||||
use InfixCrumb::LeftOperand;
|
||||
use InfixCrumb::RightOperand;
|
||||
|
||||
let code_block = r"
|
||||
d,e = p
|
||||
a = d
|
||||
b = e
|
||||
c = a + b
|
||||
fun a = a b
|
||||
f = fun 2";
|
||||
|
||||
|
||||
let run = TestRun::from_block(code_block);
|
||||
let c = &run.connections[0];
|
||||
assert_eq!(run.endpoint_node_repr(&c.source), "a = d");
|
||||
assert_eq!(&c.source.crumbs, &crumbs![LeftOperand]);
|
||||
assert_eq!(run.endpoint_node_repr(&c.destination), "c = a + b");
|
||||
assert_eq!(&c.destination.crumbs, &crumbs![RightOperand,LeftOperand]);
|
||||
|
||||
let c = &run.connections[1];
|
||||
assert_eq!(run.endpoint_node_repr(&c.source), "b = e");
|
||||
assert_eq!(&c.source.crumbs, &crumbs![LeftOperand]);
|
||||
assert_eq!(run.endpoint_node_repr(&c.destination), "c = a + b");
|
||||
assert_eq!(&c.destination.crumbs, &crumbs![RightOperand,RightOperand]);
|
||||
|
||||
let c = &run.connections[2];
|
||||
assert_eq!(run.endpoint_node_repr(&c.source), "d,e = p");
|
||||
assert_eq!(&c.source.crumbs, &crumbs![LeftOperand,LeftOperand]);
|
||||
assert_eq!(run.endpoint_node_repr(&c.destination), "a = d");
|
||||
assert_eq!(&c.destination.crumbs, &crumbs![RightOperand]);
|
||||
|
||||
let c = &run.connections[3];
|
||||
assert_eq!(run.endpoint_node_repr(&c.source), "d,e = p");
|
||||
assert_eq!(&c.source.crumbs, &crumbs![LeftOperand,RightOperand]);
|
||||
assert_eq!(run.endpoint_node_repr(&c.destination), "b = e");
|
||||
assert_eq!(&c.destination.crumbs, &crumbs![RightOperand]);
|
||||
|
||||
// Note that line `fun a = a b` des not introduce any connections, as it is a definition.
|
||||
|
||||
assert_eq!(run.connections.len(),4);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
pub fn inline_definition() {
|
||||
let run = TestRun::from_main_def("main = a");
|
||||
assert!(run.connections.is_empty());
|
||||
}
|
||||
}
|
@ -268,6 +268,13 @@ impl DefinitionInfo {
|
||||
})
|
||||
}
|
||||
|
||||
/// Tries to interpret a root line (i.e. the AST being placed in a line directly in the module
|
||||
/// scope) as a definition.
|
||||
pub fn from_root_line(line:&ast::BlockLine<Option<Ast>>) -> Option<DefinitionInfo> {
|
||||
let indent = 0;
|
||||
Self::from_line_ast(line.elem.as_ref()?,ScopeKind::Root,indent)
|
||||
}
|
||||
|
||||
/// Tries to interpret `Line`'s `Ast` as a function definition.
|
||||
///
|
||||
/// Assumes that the AST represents the contents of line (and not e.g. right-hand side of
|
||||
|
@ -10,6 +10,7 @@ use ast::Ast;
|
||||
use ast::BlockLine;
|
||||
use ast::known;
|
||||
use utils::fail::FallibleResult;
|
||||
use crate::double_representation::connection::Connection;
|
||||
|
||||
/// Graph uses the same `Id` as the definition which introduces the graph.
|
||||
pub type Id = double_representation::definition::Id;
|
||||
@ -83,6 +84,11 @@ impl GraphInfo {
|
||||
Self::from_function_binding(self.source.ast.clone())
|
||||
}
|
||||
|
||||
/// Gets the list of connections between the nodes in this graph.
|
||||
pub fn connections(&self) -> Vec<Connection> {
|
||||
double_representation::connection::list(&self.source.ast.rarg)
|
||||
}
|
||||
|
||||
fn is_node_by_id(line:&BlockLine<Option<Ast>>, id:ast::Id) -> bool {
|
||||
let node_info = line.elem.as_ref().and_then(NodeInfo::from_line_ast);
|
||||
let id_matches = node_info.map(|node| node.id() == id);
|
||||
@ -113,6 +119,11 @@ impl GraphInfo {
|
||||
self.source.set_block_lines(lines)
|
||||
}
|
||||
|
||||
/// Locates a node with the given id.
|
||||
pub fn find_node(&self,id:ast::Id) -> Option<NodeInfo> {
|
||||
self.nodes().iter().find(|node| node.id() == id).cloned()
|
||||
}
|
||||
|
||||
/// After removing last node, we want to insert a placeholder value for definition value.
|
||||
/// This defines its AST. Currently it is just `Nothing`.
|
||||
pub fn empty_graph_body() -> Ast {
|
||||
|
@ -3,11 +3,11 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use ast::Ast;
|
||||
use ast::Id;
|
||||
use ast::crumbs::Crumbable;
|
||||
use ast::known;
|
||||
|
||||
|
||||
/// Node Id is the Ast Id attached to the node's expression.
|
||||
pub type Id = ast::Id;
|
||||
|
||||
// ================
|
||||
// === NodeInfo ===
|
||||
|
44
gui/src/rust/ide/src/double_representation/test_utils.rs
Normal file
44
gui/src/rust/ide/src/double_representation/test_utils.rs
Normal file
@ -0,0 +1,44 @@
|
||||
//! General-purpose utilities for creating tests for double representation.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use regex::Captures;
|
||||
use regex::Match;
|
||||
|
||||
/// Helper type for markdown-defined test cases with `regex` library.
|
||||
/// When implementing a `Replacer`, on each match the `process_match` should be called.
|
||||
#[derive(Clone,Copy,Debug,Default)]
|
||||
pub struct MarkdownProcessor {
|
||||
markdown_bytes_consumed : usize,
|
||||
}
|
||||
|
||||
impl MarkdownProcessor {
|
||||
/// Convert index from marked to unmarked code.
|
||||
fn marked_to_unmarked_index(&self, i:usize) -> usize {
|
||||
assert!(self.markdown_bytes_consumed <= i);
|
||||
i - self.markdown_bytes_consumed
|
||||
}
|
||||
|
||||
/// Convert indices range from marked to unmarked code.
|
||||
fn marked_to_unmarked_range(&self, range:Range<usize>) -> Range<usize> {
|
||||
Range {
|
||||
start : self.marked_to_unmarked_index(range.start),
|
||||
end : self.marked_to_unmarked_index(range.end),
|
||||
}
|
||||
}
|
||||
|
||||
/// Assumes that given match is the part of capture that should be passed to the dst string.
|
||||
/// Appends the `body` match contents to the `dst` and returns its span in unmarked text.
|
||||
/// All characters in the capture that do not belong to `body` are considered markdown.
|
||||
pub fn process_match
|
||||
(&mut self, captures:&Captures, body:&Match, dst:&mut String) -> Range<usize> {
|
||||
let whole_match = captures.get(0).expect("Capture 0 should always be present.");
|
||||
let bytes_to_body = body.start() - whole_match.start();
|
||||
let bytes_after_body = whole_match.end() - body.end();
|
||||
self.markdown_bytes_consumed += bytes_to_body;
|
||||
let ret = self.marked_to_unmarked_range(body.range());
|
||||
self.markdown_bytes_consumed += bytes_after_body;
|
||||
dst.push_str(body.as_str());
|
||||
ret
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user