mirror of
https://github.com/enso-org/enso.git
synced 2025-01-01 21:15:17 +03:00
Node Discovery (https://github.com/enso-org/ide/pull/250)
This PR introduces node discovery logic in `double_representation/node.rs`.
The parser dependency has been bumped, due to #2154.
Improvements in definition discovery — fixed previous issue where first line in block was ignored.
Original commit: 1370aaf431
This commit is contained in:
parent
619eb4fabd
commit
17f729874e
@ -188,15 +188,20 @@ impl Ast {
|
||||
Ast::new_with_length(shape,id,length)
|
||||
}
|
||||
|
||||
/// As `new` but sets given declared length for the shape.
|
||||
pub fn new_with_length<S:Into<Shape<Ast>>>
|
||||
(shape:S, id:Option<ID>, len:usize) -> Ast {
|
||||
let shape = shape.into();
|
||||
/// Just wraps shape, id and len into Ast node.
|
||||
pub fn from_ast_id_len(shape:Shape<Ast>, id:Option<ID>, len:usize) -> Ast {
|
||||
let with_length = WithLength { wrapped:shape , len };
|
||||
let with_id = WithID { wrapped:with_length, id };
|
||||
Ast { wrapped: Rc::new(with_id) }
|
||||
}
|
||||
|
||||
/// As `new` but sets given declared length for the shape.
|
||||
pub fn new_with_length<S:Into<Shape<Ast>>>
|
||||
(shape:S, id:Option<ID>, len:usize) -> Ast {
|
||||
let shape = shape.into();
|
||||
Self::from_ast_id_len(shape,id,len)
|
||||
}
|
||||
|
||||
/// Iterates over all transitive child nodes (including self).
|
||||
pub fn iter_recursive(&self) -> impl Iterator<Item=&Ast> {
|
||||
internal::iterate_subtree(self)
|
||||
@ -895,18 +900,23 @@ impl Ast {
|
||||
// TODO smart constructors for other cases
|
||||
// as part of https://github.com/luna/enso/issues/338
|
||||
|
||||
pub fn number(number:i64) -> Ast {
|
||||
let number = Number {base:None,int:number.to_string()};
|
||||
Ast::from(number)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@ -916,7 +926,8 @@ impl Ast {
|
||||
Ast::from(opr)
|
||||
}
|
||||
|
||||
pub fn infix<Str0, Str1, Str2>(larg:Str0, opr:Str1, rarg:Str2) -> Ast
|
||||
/// 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
|
||||
where Str0: ToString
|
||||
, Str1: ToString
|
||||
, Str2: ToString {
|
||||
@ -1148,7 +1159,7 @@ mod tests {
|
||||
_ => "«invalid»".to_string(),
|
||||
};
|
||||
|
||||
let infix = Ast::infix("foo", "+", "bar");
|
||||
let infix = Ast::infix_var("foo", "+", "bar");
|
||||
let strings = infix.iter().map(to_string);
|
||||
let strings = strings.collect::<Vec<_>>();
|
||||
|
||||
|
@ -24,7 +24,7 @@ use std::path::PathBuf;
|
||||
const PARSER_PATH: &str = "./pkg/scala-parser.js";
|
||||
|
||||
/// Commit from `enso` repository that will be used to obtain parser from.
|
||||
const PARSER_COMMIT: &str = "083fa0e4a50c62cf5d33b06710f5294441729a9e";
|
||||
const PARSER_COMMIT: &str = "658bc26800eb03a2995af72df5a79ef21f7eb349";
|
||||
|
||||
/// Magic code that needs to be prepended to ScalaJS generated parser due to:
|
||||
/// https://github.com/scala-js/scala-js/issues/3677/
|
||||
|
@ -20,8 +20,8 @@ fn web_test() {
|
||||
|
||||
let mut parser = Parser::new_or_panic();
|
||||
|
||||
let mut parse = |input| {
|
||||
let span = Span::from((0,5));
|
||||
let mut parse = |input:&str| {
|
||||
let span = Span::from((0,input.len()));
|
||||
let ids = IdMap(vec![(span,uuid)]);
|
||||
let ast = parser.parse(String::from(input), ids).unwrap().wrapped;
|
||||
|
||||
@ -37,10 +37,10 @@ fn web_test() {
|
||||
|
||||
|
||||
let app_x_y = ast::Prefix {func: Ast::var("x"), off: 3, arg: Ast::var("y")};
|
||||
|
||||
let var_xy = ast::Var { name: "xy".into() };
|
||||
|
||||
assert_eq!(parse(""), line(None));
|
||||
assert_eq!(parse("xy"), line(Some(Ast::var("xy"))));
|
||||
assert_eq!(parse("xy"), line(Some(Ast::new(var_xy, Some(uuid)))));
|
||||
assert_eq!(parse("x y"), line(Some(Ast::new(app_x_y, Some(uuid)))));
|
||||
|
||||
}
|
||||
|
@ -201,10 +201,12 @@ mod test {
|
||||
|
||||
let uuid1 = Uuid::new_v4();
|
||||
let uuid2 = Uuid::new_v4();
|
||||
let uuid3 = Uuid::new_v4();
|
||||
let code = "2+2";
|
||||
let id_map = IdMap(vec!
|
||||
[ (Span::new(Index::new(0), Size::new(1)),uuid1.clone())
|
||||
, (Span::new(Index::new(2), Size::new(1)),uuid2)
|
||||
, (Span::new(Index::new(0), Size::new(3)),uuid3)
|
||||
]);
|
||||
|
||||
let controller = Handle::new_mock(location,code,id_map,file_manager,parser).unwrap();
|
||||
@ -223,7 +225,7 @@ mod test {
|
||||
opr : Ast::new(ast::Opr {name:"+".to_string()}, None),
|
||||
roff : 0,
|
||||
rarg : Ast::new(ast::Number{base:None, int:"2".to_string()}, Some(uuid2)),
|
||||
}, None)),
|
||||
}, Some(uuid3))),
|
||||
off: 0
|
||||
}]
|
||||
}, None);
|
||||
|
@ -2,4 +2,6 @@
|
||||
//! module.
|
||||
|
||||
pub mod definition;
|
||||
pub mod graph;
|
||||
pub mod node;
|
||||
pub mod text;
|
||||
|
@ -54,7 +54,7 @@ pub fn identifier_name(ast:&Ast) -> Option<String> {
|
||||
|
||||
/// Structure representing definition name. If this is an extension method, extended type is
|
||||
/// also included.
|
||||
#[derive(Clone,Debug,PartialEq)]
|
||||
#[derive(Clone,Debug,Eq,Hash,PartialEq)]
|
||||
pub struct DefinitionName {
|
||||
/// Used when definition is an extension method. Then it stores the segments
|
||||
/// of the extended target type path.
|
||||
@ -124,34 +124,42 @@ impl DefinitionInfo {
|
||||
pub fn body(&self) -> Ast {
|
||||
self.ast.rarg.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to interpret `Line`'s `Ast` as a function definition.
|
||||
pub fn get_definition_info
|
||||
(line:&ast::BlockLine<Option<Ast>>, kind:ScopeKind) -> Option<DefinitionInfo> {
|
||||
let ast = opr::to_assignment(line.elem.as_ref()?)?;
|
||||
/// Tries to interpret `Line`'s contents as a function definition.
|
||||
pub fn from_line
|
||||
(line:&ast::BlockLine<Option<Ast>>, kind:ScopeKind) -> Option<DefinitionInfo> {
|
||||
let ast = line.elem.as_ref()?;
|
||||
Self::from_line_ast(ast,kind)
|
||||
}
|
||||
|
||||
// There two cases - function name is either a Var or operator.
|
||||
// If this is a Var, we have Var, optionally under a Prefix chain with args.
|
||||
// If this is an operator, we have SectionRight with (if any prefix in arguments).
|
||||
let lhs = prefix::Chain::new_non_strict(&ast.larg);
|
||||
let name = DefinitionName::from_ast(&lhs.func)?;
|
||||
let args = lhs.args;
|
||||
let ret = DefinitionInfo {ast,name,args};
|
||||
/// 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
|
||||
/// some binding or other kind of subtree).
|
||||
pub fn from_line_ast(ast:&Ast, kind:ScopeKind) -> Option<DefinitionInfo> {
|
||||
let infix = opr::to_assignment(ast)?;
|
||||
// There two cases - function name is either a Var or operator.
|
||||
// If this is a Var, we have Var, optionally under a Prefix chain with args.
|
||||
// If this is an operator, we have SectionRight with (if any prefix in arguments).
|
||||
let lhs = prefix::Chain::new_non_strict(&infix.larg);
|
||||
let name = DefinitionName::from_ast(&lhs.func)?;
|
||||
let args = lhs.args;
|
||||
let ret = DefinitionInfo {ast:infix,name,args};
|
||||
|
||||
// Note [Scope Differences]
|
||||
if kind == ScopeKind::NonRoot {
|
||||
// 1. Not an extension method but setter.
|
||||
let is_setter = !ret.name.extended_target.is_empty();
|
||||
// 2. No explicit args -- this is a node, not a definition.
|
||||
let is_node = ret.args.is_empty();
|
||||
if is_setter || is_node {
|
||||
None
|
||||
// Note [Scope Differences]
|
||||
if kind == ScopeKind::NonRoot {
|
||||
// 1. Not an extension method but setter.
|
||||
let is_setter = !ret.name.extended_target.is_empty();
|
||||
// 2. No explicit args -- this is a node, not a definition.
|
||||
let is_node = ret.args.is_empty();
|
||||
if is_setter || is_node {
|
||||
None
|
||||
} else {
|
||||
Some(ret)
|
||||
}
|
||||
} else {
|
||||
Some(ret)
|
||||
}
|
||||
} else {
|
||||
Some(ret)
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,40 +173,55 @@ pub fn get_definition_info
|
||||
// this parameter). In definition, this is just a node (evaluated expression).
|
||||
|
||||
|
||||
/// Either ast::Block or Module's root contents.
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct GeneralizedBlock<'a> {
|
||||
/// If this is a root-scope (module) or nested scope.
|
||||
pub kind : ScopeKind,
|
||||
/// Lines placed directly in this scope.
|
||||
pub lines : &'a Vec<ast::BlockLine<Option<Ast>>>,
|
||||
}
|
||||
|
||||
impl<'a> GeneralizedBlock<'a> {
|
||||
/// Wrap `Module` into `GeneralizedBlock`.
|
||||
pub fn from_module(module:&'a ast::Module<Ast>) -> GeneralizedBlock<'a> {
|
||||
GeneralizedBlock { kind:ScopeKind::Root, lines:&module.lines }
|
||||
}
|
||||
/// Wrap `Block` into `GeneralizedBlock`.
|
||||
pub fn from_block(block:&'a ast::Block<Ast>) -> GeneralizedBlock<'a> {
|
||||
GeneralizedBlock { kind:ScopeKind::NonRoot, lines:&block.lines }
|
||||
// ==========================
|
||||
// === DefinitionProvider ===
|
||||
// ==========================
|
||||
|
||||
/// An entity that contains lines that we want to interpret as definitions.
|
||||
pub trait DefinitionProvider {
|
||||
/// What kind of scope this is.
|
||||
fn scope_kind() -> ScopeKind;
|
||||
|
||||
/// Iterates over non-empty lines' ASTs.
|
||||
fn line_asts<'a>(&'a self) -> Box<dyn Iterator<Item=&'a Ast> + 'a>;
|
||||
|
||||
/// Lists all the definitions in the entity.
|
||||
fn list_definitions(&self) -> Vec<DefinitionInfo> {
|
||||
self.line_asts().flat_map(|ast| {
|
||||
DefinitionInfo::from_line_ast(ast,Self::scope_kind())
|
||||
}).collect()
|
||||
}
|
||||
|
||||
/// Returns information about all definition defined in this block.
|
||||
pub fn list_definitions(&self) -> Vec<DefinitionInfo> {
|
||||
self.lines.iter().flat_map(|ast| get_definition_info(ast,self.kind)).collect()
|
||||
}
|
||||
|
||||
/// Goes through definitions introduced in this block and returns one with matching name.
|
||||
pub fn find_definition(&self, name:&DefinitionName) -> Option<DefinitionInfo> {
|
||||
self.lines.iter().find_map(|ast| {
|
||||
let definition = get_definition_info(ast, self.kind)?;
|
||||
/// Tries to find definition by given name in the entity.
|
||||
fn find_definition(&self, name:&DefinitionName) -> Option<DefinitionInfo> {
|
||||
self.line_asts().find_map(|ast| {
|
||||
let definition = DefinitionInfo::from_line_ast(ast, Self::scope_kind())?;
|
||||
let matches = &definition.name == name;
|
||||
matches.as_some(definition)
|
||||
})
|
||||
})}
|
||||
}
|
||||
|
||||
impl DefinitionProvider for known::Module {
|
||||
fn scope_kind() -> ScopeKind { ScopeKind::Root }
|
||||
fn line_asts<'a>(&'a self) -> Box<dyn Iterator<Item=&'a Ast> + 'a> {
|
||||
Box::new(self.iter())
|
||||
}
|
||||
}
|
||||
|
||||
impl DefinitionProvider for known::Block {
|
||||
fn scope_kind() -> ScopeKind { ScopeKind::NonRoot }
|
||||
fn line_asts<'a>(&'a self) -> Box<dyn Iterator<Item=&'a Ast> + 'a> {
|
||||
Box::new(self.iter())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Tests ===
|
||||
// =============
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -247,27 +270,26 @@ mod tests {
|
||||
let expected_def_names_in_def = vec!["add", "*"];
|
||||
|
||||
// === Program with defnitions in root ===
|
||||
let program = definition_lines.join("\n");
|
||||
let module = parser.parse_module(program.into(), default()).unwrap();
|
||||
let block = GeneralizedBlock::from_module(&*module);
|
||||
let definitions = block.list_definitions();
|
||||
let program = definition_lines.join("\n");
|
||||
let module = parser.parse_module(program.into(), default()).unwrap();
|
||||
let definitions = module.list_definitions();
|
||||
assert_eq_strings(to_names(&definitions),expected_def_names_in_module);
|
||||
|
||||
// Check that definition can be found and their body is properly described.
|
||||
let add_name = DefinitionName::new_plain("add");
|
||||
let add = block.find_definition(&add_name).expect("failed to find `add` function");
|
||||
let add = module.find_definition(&add_name).expect("failed to find `add` function");
|
||||
let body = known::Number::try_new(add.body()).expect("add body should be a Block");
|
||||
assert_eq!(body.int,"50");
|
||||
|
||||
// === Program with definition in `some_func`'s body `Block` ===
|
||||
let indented_lines = definition_lines.iter().map(indented).collect_vec();
|
||||
let program = format!("some_func arg1 arg2 =\n{}", indented_lines.join("\n"));
|
||||
let root_block = parser.parse_module(program,default()).unwrap();
|
||||
let root_defs = GeneralizedBlock::from_module(&*root_block).list_definitions();
|
||||
let module = parser.parse_module(program,default()).unwrap();
|
||||
let root_defs = module.list_definitions();
|
||||
let (only_def,) = root_defs.expect_tuple();
|
||||
assert_eq!(&only_def.name.to_string(),"some_func");
|
||||
let body_block = known::Block::try_from(only_def.body()).unwrap();
|
||||
let nested_defs = GeneralizedBlock::from_block(&body_block).list_definitions();
|
||||
let nested_defs = body_block.list_definitions();
|
||||
assert_eq_strings(to_names(&nested_defs),expected_def_names_in_def);
|
||||
}
|
||||
}
|
||||
|
133
gui/lib/ide/src/double_representation/graph.rs
Normal file
133
gui/lib/ide/src/double_representation/graph.rs
Normal file
@ -0,0 +1,133 @@
|
||||
//! Code for retrieving graph description from AST.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::double_representation::definition;
|
||||
use crate::double_representation::node;
|
||||
|
||||
use ast::Ast;
|
||||
use ast::known;
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === GraphInfo ===
|
||||
// =================
|
||||
|
||||
/// Description of the graph, based on information available in AST.
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct GraphInfo {
|
||||
name : definition::DefinitionName,
|
||||
args : Vec<Ast>,
|
||||
/// Describes all known nodes in this graph (does not include special pseudo-nodes like graph
|
||||
/// inputs and outputs).
|
||||
pub nodes:Vec<node::NodeInfo>,
|
||||
}
|
||||
|
||||
impl GraphInfo {
|
||||
/// Describe graph of the given definition.
|
||||
pub fn from_definition(info:&definition::DefinitionInfo) -> GraphInfo {
|
||||
let name = info.name.clone();
|
||||
let args = info.args.clone();
|
||||
let nodes = Self::from_function_binding(info.ast.clone());
|
||||
GraphInfo {name,args,nodes}
|
||||
}
|
||||
|
||||
/// Lists nodes in the given binding's ast (infix expression).
|
||||
fn from_function_binding(ast:known::Infix) -> Vec<node::NodeInfo> {
|
||||
let body = ast.rarg.clone();
|
||||
if let Ok(body_block) = known::Block::try_new(body.clone()) {
|
||||
block_nodes(&body_block)
|
||||
} else {
|
||||
expression_node(body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === Listing nodes ===
|
||||
// =====================
|
||||
|
||||
/// Collects information about nodes in given code `Block`.
|
||||
pub fn block_nodes(ast:&known::Block) -> Vec<node::NodeInfo> {
|
||||
ast.iter().flat_map(|line_ast| {
|
||||
// If this can be a definition, then don't treat it as a node.
|
||||
match definition::DefinitionInfo::from_line_ast(line_ast, definition::ScopeKind::NonRoot) {
|
||||
None => node::NodeInfo::from_line_ast(line_ast),
|
||||
Some(_) => None
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
|
||||
/// Collects information about nodes in given trivial definition body.
|
||||
pub fn expression_node(ast:Ast) -> Vec<node::NodeInfo> {
|
||||
node::NodeInfo::new_expression(ast).into_iter().collect()
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Tests ===
|
||||
// =============
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::double_representation::definition::DefinitionName;
|
||||
use crate::double_representation::definition::DefinitionProvider;
|
||||
|
||||
use parser::api::IsParser;
|
||||
use wasm_bindgen_test::wasm_bindgen_test;
|
||||
|
||||
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
/// Takes a program with main definition in root and returns main's graph.
|
||||
fn main_graph(parser:&mut impl IsParser, program:impl Str) -> GraphInfo {
|
||||
let module = parser.parse_module(program.into(), default()).unwrap();
|
||||
let name = DefinitionName::new_plain("main");
|
||||
let main = module.find_definition(&name).unwrap();
|
||||
println!("{:?}",module);
|
||||
GraphInfo::from_definition(&main)
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn detect_a_node() {
|
||||
let mut parser = parser::Parser::new_or_panic();
|
||||
// Each of these programs should have a `main` definition with a single `2+2` node.
|
||||
let programs = vec![
|
||||
"main = 2+2",
|
||||
"main = \n 2+2",
|
||||
"main = \n foo = 2+2",
|
||||
"main = \n foo = 2+2\n bar b = 2+2", // `bar` is a definition, not a node
|
||||
];
|
||||
for program in programs {
|
||||
let graph = main_graph(&mut parser, program);
|
||||
assert_eq!(graph.nodes.len(), 1);
|
||||
let node = &graph.nodes[0];
|
||||
assert_eq!(node.expression_text(), "2+2");
|
||||
let _ = node.id(); // just to make sure it is available
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn multiple_node_graph() {
|
||||
let mut parser = parser::Parser::new_or_panic();
|
||||
let program = r"
|
||||
main =
|
||||
foo = node
|
||||
foo a = not_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);
|
||||
assert_eq!(graph.nodes.len(), 2);
|
||||
for node in graph.nodes.iter() {
|
||||
assert_eq!(node.expression_text(), "node");
|
||||
}
|
||||
}
|
||||
}
|
110
gui/lib/ide/src/double_representation/node.rs
Normal file
110
gui/lib/ide/src/double_representation/node.rs
Normal file
@ -0,0 +1,110 @@
|
||||
//! Code for node discovery and other node-related tasks.
|
||||
|
||||
use ast::Ast;
|
||||
use ast::HasRepr;
|
||||
use ast::ID;
|
||||
use ast::known;
|
||||
|
||||
|
||||
|
||||
// ================
|
||||
// === NodeInfo ===
|
||||
// ================
|
||||
|
||||
/// Description of the node that consists of all information locally available about node.
|
||||
/// Nodes are required to bear IDs. This enum should never contain an ast of node without id set.
|
||||
#[derive(Clone,Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum NodeInfo {
|
||||
/// Code with assignment, e.g. `foo = 2 + 2`
|
||||
Binding { infix: known::Infix },
|
||||
/// Code without assignment (no variable binding), e.g. `2 + 2`.
|
||||
Expression { ast: Ast },
|
||||
}
|
||||
|
||||
impl NodeInfo {
|
||||
/// Tries to interpret the whole binding as a node. Right-hand side will become node's
|
||||
/// expression.
|
||||
pub fn new_binding(infix:known::Infix) -> Option<NodeInfo> {
|
||||
infix.rarg.id?;
|
||||
Some(NodeInfo::Binding {infix})
|
||||
}
|
||||
|
||||
/// Tries to interpret AST as node, treating whole AST as an expression.
|
||||
pub fn new_expression(ast:Ast) -> Option<NodeInfo> {
|
||||
ast.id?;
|
||||
Some(NodeInfo::Expression {ast})
|
||||
}
|
||||
|
||||
/// Tries to interpret AST as node, treating whole AST as an expression.
|
||||
pub fn from_line_ast(ast:&Ast) -> Option<NodeInfo> {
|
||||
if let Some(infix) = ast::opr::to_assignment(ast) {
|
||||
Self::new_binding(infix)
|
||||
} else {
|
||||
Self::new_expression(ast.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Node's unique ID.
|
||||
pub fn id(&self) -> ID {
|
||||
// Panic must not happen, as the only available constructor checks that
|
||||
// there is an ID present.
|
||||
self.expression_ast().id.expect("Node AST must bear an ID")
|
||||
}
|
||||
|
||||
/// AST of the node's expression.
|
||||
pub fn expression_ast(&self) -> &Ast {
|
||||
match self {
|
||||
NodeInfo::Binding {infix} => &infix.rarg,
|
||||
NodeInfo::Expression{ast} => &ast,
|
||||
}
|
||||
}
|
||||
|
||||
/// The node's expression textual representation.
|
||||
pub fn expression_text(&self) -> String {
|
||||
self.expression_ast().repr()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Tests ===
|
||||
// =============
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use wasm_bindgen_test::wasm_bindgen_test;
|
||||
|
||||
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
fn expect_node(ast:Ast, expression_text:&str, id:ID) {
|
||||
let node_info = NodeInfo::from_line_ast(&ast).expect("expected a node");
|
||||
assert_eq!(node_info.expression_text(),expression_text);
|
||||
assert_eq!(node_info.id(), id);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn expression_node_test() {
|
||||
// expression: `4`
|
||||
let id = ID::new_v4();
|
||||
let ast = Ast::new(ast::Number { base:None, int: "4".into()}, Some(id));
|
||||
expect_node(ast,"4",id);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn binding_node_test() {
|
||||
// expression: `foo = 4`
|
||||
let id = ID::new_v4();
|
||||
let number = ast::Number { base:None, int: "4".into()};
|
||||
let larg = Ast::var("foo");
|
||||
let loff = 1;
|
||||
let opr = Ast::opr("=");
|
||||
let roff = 1;
|
||||
let rarg = Ast::new(number, Some(id));
|
||||
let ast = Ast::new(ast::Infix {larg,loff,opr,roff,rarg}, None);
|
||||
expect_node(ast,"4",id);
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@ pub mod prelude {
|
||||
|
||||
pub use crate::constants;
|
||||
pub use crate::controller;
|
||||
pub use crate::double_representation;
|
||||
pub use crate::executor;
|
||||
|
||||
pub use futures::Future;
|
||||
|
Loading…
Reference in New Issue
Block a user