mirror of
https://github.com/enso-org/enso.git
synced 2024-12-18 16:22:17 +03:00
Node Operations (https://github.com/enso-org/ide/pull/298)
Original commit: cf01ebf061
This commit is contained in:
parent
6552176247
commit
fd269d2457
88
gui/src/js/package-lock.json
generated
88
gui/src/js/package-lock.json
generated
@ -1982,8 +1982,7 @@
|
||||
"base64-js": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
|
||||
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==",
|
||||
"dev": true
|
||||
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
|
||||
},
|
||||
"batch": {
|
||||
"version": "0.6.1",
|
||||
@ -2032,13 +2031,24 @@
|
||||
}
|
||||
},
|
||||
"bl": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.0.1.tgz",
|
||||
"integrity": "sha512-FL/TdvchukRCuWVxT0YMO/7+L5TNeNrVFvRU2IY63aUyv9mpt8splf2NEr6qXtPo5fya5a66YohQKvGNmLrWNA==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz",
|
||||
"integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==",
|
||||
"requires": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"buffer": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.5.0.tgz",
|
||||
"integrity": "sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww==",
|
||||
"requires": {
|
||||
"base64-js": "^1.0.2",
|
||||
"ieee754": "^1.1.4"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
@ -4227,43 +4237,6 @@
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"requires": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"yargs": {
|
||||
"version": "15.3.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz",
|
||||
"integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==",
|
||||
"requires": {
|
||||
"cliui": "^6.0.0",
|
||||
"decamelize": "^1.2.0",
|
||||
"find-up": "^4.1.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^4.2.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^18.1.1"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "18.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.1.tgz",
|
||||
"integrity": "sha512-KRHEsOM16IX7XuLnMOqImcPNbLVXMNHYAoFc3BKR8Ortl5gzDbtXvvEoGx9imk5E+X1VeNKNlcHr8B8vi+7ipA==",
|
||||
"requires": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -6577,8 +6550,7 @@
|
||||
"ieee754": {
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
|
||||
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
|
||||
},
|
||||
"iferr": {
|
||||
"version": "0.1.5",
|
||||
@ -7134,9 +7106,9 @@
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"isbinaryfile": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.4.tgz",
|
||||
"integrity": "sha512-pEutbN134CzcjlLS1myKX/uxNjwU5eBVSprvkpv3+3dqhBHUZLIWJQowC40w5c0Zf19vBY8mrZl88y5J4RAPbQ=="
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.5.tgz",
|
||||
"integrity": "sha512-Jvz0gpTh1AILHMCBUyqq7xv1ZOQrxTDwyp1/QUq1xFpOBvp4AH5uEobPePJht8KnBGqQIH7We6OR73mXsjG0cA=="
|
||||
},
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
@ -9243,11 +9215,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz",
|
||||
"integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==",
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.2.tgz",
|
||||
"integrity": "sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
@ -12203,9 +12175,9 @@
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
|
||||
},
|
||||
"yargs": {
|
||||
"version": "15.3.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.0.tgz",
|
||||
"integrity": "sha512-g/QCnmjgOl1YJjGsnUg2SatC7NUYEiLXJqxNOQU9qSpjzGtGXda9b+OKccr1kLTy8BN9yqEyqfq5lxlwdc13TA==",
|
||||
"version": "15.3.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz",
|
||||
"integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==",
|
||||
"requires": {
|
||||
"cliui": "^6.0.0",
|
||||
"decamelize": "^1.2.0",
|
||||
@ -12217,7 +12189,7 @@
|
||||
"string-width": "^4.2.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^18.1.0"
|
||||
"yargs-parser": "^18.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
@ -12328,9 +12300,9 @@
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "18.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.0.tgz",
|
||||
"integrity": "sha512-o/Jr6JBOv6Yx3pL+5naWSoIA2jJ+ZkMYQG/ie9qFbukBe4uzmBatlXFOiu/tNKRWEtyf+n5w7jc/O16ufqOTdQ==",
|
||||
"version": "18.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.1.tgz",
|
||||
"integrity": "sha512-KRHEsOM16IX7XuLnMOqImcPNbLVXMNHYAoFc3BKR8Ortl5gzDbtXvvEoGx9imk5E+X1VeNKNlcHr8B8vi+7ipA==",
|
||||
"requires": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
|
@ -8,14 +8,14 @@ edition = "2018"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
derive_more = { version = "0.15.0" }
|
||||
failure = { version = "0.1.5" }
|
||||
lazy_static = { version = "1.4.0" }
|
||||
regex = { version = "1" }
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_json = { version = "1.0" }
|
||||
shrinkwraprs = { version = "0.2.1" }
|
||||
uuid = { version = "0.8.1", features = ["serde", "v4"] }
|
||||
derive_more = { version = "0.15.0" }
|
||||
failure = { version = "0.1.5" }
|
||||
lazy_static = { version = "1.4.0" }
|
||||
regex = { version = "1" }
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_json = { version = "1.0" }
|
||||
shrinkwraprs = { version = "0.2.1" }
|
||||
uuid = { version = "0.8.1", features = ["serde","v4","wasm-bindgen"] }
|
||||
|
||||
ast-macros = { version = "0.1.0", path = "../macros" }
|
||||
data = { version = "0.1.0", path = "../../../lib/data" }
|
||||
|
638
gui/src/rust/ide/ast/impl/src/crumbs.rs
Normal file
638
gui/src/rust/ide/ast/impl/src/crumbs.rs
Normal file
@ -0,0 +1,638 @@
|
||||
//! Crumbs for AST. Crumb identifies children node location in AST node. The access should be
|
||||
//! possible in a constant time.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::known;
|
||||
use crate::Shape;
|
||||
use utils::fail::FallibleResult;
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Errors ===
|
||||
// ==============
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[fail(display = "The crumb refers to line by index that is out of bounds.")]
|
||||
#[derive(Debug,Fail,Clone,Copy)]
|
||||
pub struct LineIndexOutOfBounds;
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Fail,Clone)]
|
||||
#[fail(display = "The line designated by crumb {:?} does not contain any AST. Context AST was {}.",
|
||||
crumb,repr)]
|
||||
pub struct LineDoesNotContainAst {
|
||||
repr : String,
|
||||
crumb : Crumb,
|
||||
}
|
||||
|
||||
impl LineDoesNotContainAst {
|
||||
/// Creates a new instance of error about missing AST in the designated line.
|
||||
pub fn new(repr:impl HasRepr, crumb:impl Into<Crumb>) -> LineDoesNotContainAst {
|
||||
let repr = repr.repr();
|
||||
let crumb = crumb.into();
|
||||
LineDoesNotContainAst {repr,crumb}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Display,Fail,Clone,Copy)]
|
||||
struct MismatchedCrumbType;
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Crumb ===
|
||||
// =============
|
||||
|
||||
// === Ast ===
|
||||
|
||||
/// Sequence of `Crumb`s describing traversal path through AST.
|
||||
pub type Crumbs = Vec<Crumb>;
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// As `Ast` can store any `Shape`, this `Crumb` may store any `Shape`-specific crumb type.
|
||||
///
|
||||
/// The location format should allow constant time traversal step, so e.g. indices should be used
|
||||
/// rather than names or similar.
|
||||
///
|
||||
/// Crumbs are potentially invalidated by any AST change.
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Hash)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Crumb {
|
||||
Block(BlockCrumb),
|
||||
Module(ModuleCrumb),
|
||||
Infix(InfixCrumb),
|
||||
}
|
||||
|
||||
|
||||
// === Block ===
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Hash)]
|
||||
pub enum BlockCrumb {
|
||||
/// The first non-empty line in block.
|
||||
HeadLine,
|
||||
/// Index in the sequence of "rest of" lines (not counting the HeadLine).
|
||||
TailLine {tail_index:usize},
|
||||
}
|
||||
|
||||
|
||||
// === Module ===
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Hash)]
|
||||
pub struct ModuleCrumb {pub line_index:usize}
|
||||
|
||||
|
||||
// === Infix ===
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Copy,Debug,PartialEq,Hash)]
|
||||
pub enum InfixCrumb {
|
||||
LeftOperand,
|
||||
Operator,
|
||||
RightOperand,
|
||||
}
|
||||
|
||||
// === Conversion Traits ===
|
||||
|
||||
impl From<BlockCrumb> for Crumb {
|
||||
fn from(crumb: BlockCrumb) -> Self {
|
||||
Crumb::Block(crumb)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&BlockCrumb> for Crumb {
|
||||
fn from(crumb: &BlockCrumb) -> Self {
|
||||
Crumb::Block(*crumb)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ModuleCrumb> for Crumb {
|
||||
fn from(crumb: ModuleCrumb) -> Self {
|
||||
Crumb::Module(crumb)
|
||||
}
|
||||
}
|
||||
impl From<&ModuleCrumb> for Crumb {
|
||||
fn from(crumb: &ModuleCrumb) -> Self {
|
||||
Crumb::Module(*crumb)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InfixCrumb> for Crumb {
|
||||
fn from(crumb: InfixCrumb) -> Self {
|
||||
Crumb::Infix(crumb)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&InfixCrumb> for Crumb {
|
||||
fn from(crumb: &InfixCrumb) -> Self {
|
||||
Crumb::Infix(*crumb)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Crumbable ===
|
||||
// =================
|
||||
|
||||
/// Interface for items that allow getting/setting stored Ast located by arbitrary `Crumb`.
|
||||
pub trait Crumbable {
|
||||
/// Specific `Crumb` type used by `Self` to locate child Asts.
|
||||
type Crumb : Into<Crumb>;
|
||||
|
||||
/// Retrieves `Ast` under the crumb.
|
||||
fn get(&self, crumb:&Self::Crumb) -> FallibleResult<&Ast>;
|
||||
|
||||
/// Sets `Ast` under the crumb, returns updated entity.
|
||||
fn set(&self, crumb:&Self::Crumb, new_ast:Ast) -> FallibleResult<Self> where Self:Sized;
|
||||
|
||||
/// Iterates all valid crumbs available for `self`.
|
||||
fn iter_subcrumbs<'a>(&'a self) -> Box<dyn Iterator<Item = Self::Crumb> + 'a>;
|
||||
|
||||
/// Iterates pairs (crumb,child_ast) for `self`.
|
||||
fn enumerate<'a>(&'a self) -> Box<dyn Iterator<Item = (Self::Crumb,&'a Ast)> + 'a> {
|
||||
let indices = self.iter_subcrumbs();
|
||||
let iter = indices.map(move |crumb| {
|
||||
// NOTE Safe if this module is correct - children crumbs are always accessible.
|
||||
let child = self.get(&crumb).unwrap();
|
||||
(crumb,child)
|
||||
});
|
||||
Box::new(iter)
|
||||
}
|
||||
}
|
||||
|
||||
impl Crumbable for crate::Infix<Ast> {
|
||||
type Crumb = InfixCrumb;
|
||||
|
||||
fn get(&self, crumb:&Self::Crumb) -> FallibleResult<&Ast> {
|
||||
let ret = match crumb {
|
||||
InfixCrumb::LeftOperand => &self.larg,
|
||||
InfixCrumb::Operator => &self.opr ,
|
||||
InfixCrumb::RightOperand => &self.rarg,
|
||||
};
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn set(&self, crumb:&Self::Crumb, new_ast:Ast) -> FallibleResult<Self> {
|
||||
let mut ret = self.clone();
|
||||
let target = match crumb {
|
||||
InfixCrumb::LeftOperand => &mut ret.larg,
|
||||
InfixCrumb::Operator => &mut ret.opr ,
|
||||
InfixCrumb::RightOperand => &mut ret.rarg,
|
||||
};
|
||||
*target = new_ast;
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn iter_subcrumbs(&self) -> Box<dyn Iterator<Item = Self::Crumb>> {
|
||||
const CHILDREN: [InfixCrumb; 3] = [InfixCrumb::LeftOperand, InfixCrumb::Operator, InfixCrumb::RightOperand];
|
||||
Box::new(CHILDREN.iter().copied())
|
||||
}
|
||||
}
|
||||
|
||||
impl Crumbable for crate::Module<Ast> {
|
||||
type Crumb = ModuleCrumb;
|
||||
|
||||
fn get(&self, crumb:&Self::Crumb) -> FallibleResult<&Ast> {
|
||||
let line = self.lines.get(crumb.line_index).ok_or(LineIndexOutOfBounds)?;
|
||||
line.elem.as_ref().ok_or_else(|| LineDoesNotContainAst::new(self,crumb).into())
|
||||
}
|
||||
|
||||
fn set(&self, crumb:&Self::Crumb, new_ast:Ast) -> FallibleResult<Self> {
|
||||
let mut module = self.clone();
|
||||
let line = module.lines.get_mut(crumb.line_index).ok_or(LineIndexOutOfBounds)?;
|
||||
line.elem.replace(new_ast);
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
fn iter_subcrumbs<'a>(&'a self) -> Box<dyn Iterator<Item = Self::Crumb> + 'a> {
|
||||
let indices = non_empty_line_indices(self.lines.iter());
|
||||
let crumbs = indices.map(|line_index| ModuleCrumb {line_index});
|
||||
Box::new(crumbs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Crumbable for crate::Block<Ast> {
|
||||
type Crumb = BlockCrumb;
|
||||
|
||||
fn get(&self, crumb:&Self::Crumb) -> FallibleResult<&Ast> {
|
||||
match crumb {
|
||||
BlockCrumb::HeadLine => Ok(&self.first_line.elem),
|
||||
BlockCrumb::TailLine {tail_index} => {
|
||||
let line = self.lines.get(*tail_index).ok_or(LineIndexOutOfBounds)?;
|
||||
line.elem.as_ref().ok_or_else(|| LineDoesNotContainAst::new(self,crumb).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set(&self, crumb:&Self::Crumb, new_ast:Ast) -> FallibleResult<Self> {
|
||||
let mut block = self.clone();
|
||||
match crumb {
|
||||
BlockCrumb::HeadLine => block.first_line.elem = new_ast,
|
||||
BlockCrumb::TailLine {tail_index} => {
|
||||
let line = block.lines.get_mut(*tail_index).ok_or(LineIndexOutOfBounds)?;
|
||||
line.elem.replace(new_ast);
|
||||
}
|
||||
}
|
||||
Ok(block)
|
||||
}
|
||||
|
||||
fn iter_subcrumbs<'a>(&'a self) -> Box<dyn Iterator<Item = Self::Crumb> + 'a> {
|
||||
let first_line = std::iter::once(BlockCrumb::HeadLine);
|
||||
let tail_line_indices = non_empty_line_indices(self.lines.iter());
|
||||
let tail_lines = tail_line_indices.map(|tail_index| {
|
||||
BlockCrumb::TailLine {tail_index}
|
||||
});
|
||||
Box::new(first_line.chain(tail_lines))
|
||||
}
|
||||
}
|
||||
|
||||
impl Crumbable for Shape<Ast> {
|
||||
type Crumb = Crumb;
|
||||
fn get(&self, crumb:&Self::Crumb) -> FallibleResult<&Ast> {
|
||||
match (self,crumb) {
|
||||
(Shape::Block(shape), Crumb::Block(crumb)) => shape.get(crumb),
|
||||
(Shape::Module(shape),Crumb::Module(crumb)) => shape.get(crumb),
|
||||
(Shape::Infix(shape), Crumb::Infix(crumb)) => shape.get(crumb),
|
||||
_ => Err(MismatchedCrumbType.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn set(&self, crumb:&Self::Crumb, new_ast:Ast) -> FallibleResult<Self> {
|
||||
match (self,crumb) {
|
||||
(Shape::Block(shape), Crumb::Block(crumb)) => Ok(shape.set(crumb,new_ast)?.into()),
|
||||
(Shape::Module(shape), Crumb::Module(crumb)) => Ok(shape.set(crumb,new_ast)?.into()),
|
||||
(Shape::Infix(shape), Crumb::Infix(crumb)) => Ok(shape.set(crumb,new_ast)?.into()),
|
||||
_ => Err(MismatchedCrumbType.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn iter_subcrumbs<'a>(&'a self) -> Box<dyn Iterator<Item = Self::Crumb> + 'a> {
|
||||
match self {
|
||||
Shape::Block(shape) => Box::new(shape.iter_subcrumbs().map(Crumb::Block)),
|
||||
Shape::Module(shape) => Box::new(shape.iter_subcrumbs().map(Crumb::Module)),
|
||||
Shape::Infix(shape) => Box::new(shape.iter_subcrumbs().map(Crumb::Infix)),
|
||||
_ => Box::new(std::iter::empty()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Just delegates the implementation to shape.
|
||||
impl Crumbable for Ast {
|
||||
type Crumb = Crumb;
|
||||
|
||||
fn get(&self, crumb:&Self::Crumb) -> FallibleResult<&Ast> {
|
||||
self.shape().get(crumb)
|
||||
}
|
||||
|
||||
fn set(&self, crumb:&Self::Crumb, new_ast:Ast) -> FallibleResult<Self> {
|
||||
let new_shape = self.shape().set(crumb,new_ast)?;
|
||||
Ok(self.with_shape(new_shape))
|
||||
|
||||
}
|
||||
|
||||
fn iter_subcrumbs<'a>(&'a self) -> Box<dyn Iterator<Item = Self::Crumb> + 'a> {
|
||||
self.shape().iter_subcrumbs()
|
||||
}
|
||||
}
|
||||
|
||||
/// Just delegates to Ast.
|
||||
impl<T,E> Crumbable for known::KnownAst<T>
|
||||
where for<'t> &'t Shape<Ast> : TryInto<&'t T, Error=E>,
|
||||
E : failure::Fail {
|
||||
type Crumb = Crumb;
|
||||
|
||||
fn get(&self, crumb:&Self::Crumb) -> FallibleResult<&Ast> { self.ast().get(crumb) }
|
||||
|
||||
fn set(&self, crumb:&Self::Crumb, new_ast:Ast) -> FallibleResult<Self> {
|
||||
let new_ast = self.ast().set(crumb,new_ast)?;
|
||||
let ret = known::KnownAst::try_new(new_ast)?;
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn iter_subcrumbs<'a>(&'a self) -> Box<dyn Iterator<Item = Self::Crumb> + 'a> {
|
||||
self.ast().iter_subcrumbs()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===========================
|
||||
// === Recursive Traversal ===
|
||||
// ===========================
|
||||
|
||||
/// Interface for recursive AST traversal using `Crumb` sequence.
|
||||
///
|
||||
/// Intended for `Ast` and `Ast`-like types, like `KnownAst`.
|
||||
pub trait TraversableAst:Sized {
|
||||
/// Returns rewritten AST where child AST under location designated by `crumbs` is updated.
|
||||
///
|
||||
/// Works recursively.
|
||||
fn set_traversing(&self, crumbs:&[Crumb], new_ast:Ast) -> FallibleResult<Self>;
|
||||
|
||||
/// Recursively traverses AST to retrieve AST node located by given crumbs sequence.
|
||||
fn get_traversing<'a>(&'a self, crumbs:&[Crumb]) -> FallibleResult<&'a Ast>;
|
||||
}
|
||||
|
||||
impl TraversableAst for Ast {
|
||||
fn set_traversing(&self, crumbs:&[Crumb], new_ast:Ast) -> FallibleResult<Self> {
|
||||
let updated_ast = if let Some(first_crumb) = crumbs.first() {
|
||||
let child = self.get(first_crumb)?;
|
||||
let updated_child = child.set_traversing(&crumbs[1..], new_ast)?;
|
||||
self.set(first_crumb,updated_child)?
|
||||
} else {
|
||||
new_ast
|
||||
};
|
||||
Ok(updated_ast)
|
||||
}
|
||||
|
||||
fn get_traversing<'a>(&'a self, crumbs:&[Crumb]) -> FallibleResult<&'a Ast> {
|
||||
if let Some(first_crumb) = crumbs.first() {
|
||||
let child = self.get(first_crumb)?;
|
||||
child.get_traversing(&crumbs[1..])
|
||||
} else {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T,E> TraversableAst for known::KnownAst<T>
|
||||
where for<'t> &'t Shape<Ast> : TryInto<&'t T, Error=E>,
|
||||
E : failure::Fail {
|
||||
fn set_traversing(&self, crumbs:&[Crumb], new_ast:Ast) -> FallibleResult<Self> {
|
||||
let updated_ast = self.ast().set_traversing(crumbs,new_ast)?;
|
||||
Ok(Self::try_new(updated_ast)?)
|
||||
}
|
||||
|
||||
fn get_traversing<'a>(&'a self, crumbs:&[Crumb]) -> FallibleResult<&'a Ast> {
|
||||
self.ast().get_traversing(crumbs)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
// === Utility ===
|
||||
// ===============
|
||||
|
||||
/// Iterates over indices of non-empty lines in a line sequence.
|
||||
pub fn non_empty_line_indices<'a, T:'a>
|
||||
(iter:impl Iterator<Item = &'a crate::BlockLine<Option<T>>> + 'a)
|
||||
-> impl Iterator<Item=usize> + 'a {
|
||||
iter.enumerate().filter_map(|(line_index,line)| {
|
||||
line.elem.as_ref().map(|_| line_index)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
// === Located ===
|
||||
// ===============
|
||||
|
||||
/// Item which location is identified by `Crumbs`.
|
||||
#[derive(Clone,Debug,Shrinkwrap)]
|
||||
pub struct Located<T> {
|
||||
/// Crumbs from containing parent.
|
||||
pub crumbs : Crumbs,
|
||||
/// The sub-item representation.
|
||||
#[shrinkwrap(main_field)]
|
||||
pub item : T
|
||||
}
|
||||
|
||||
impl<T> Located<T> {
|
||||
/// Creates a new located item.
|
||||
pub fn new(crumbs:Crumbs, item:T) -> Located<T> {
|
||||
Located {crumbs,item}
|
||||
}
|
||||
|
||||
/// Creates a new item in a root location (empty crumbs list).
|
||||
pub fn new_root(item:T) -> Located<T> {
|
||||
let crumbs = default();
|
||||
Located {crumbs,item}
|
||||
}
|
||||
|
||||
/// Creates a new item in a root location (single crumb location).
|
||||
pub fn new_direct_child(crumb:impl Into<Crumb>, item:T) -> Located<T> {
|
||||
let crumbs = vec![crumb.into()];
|
||||
Located {crumbs,item}
|
||||
}
|
||||
|
||||
/// Uses given function to map over the item.
|
||||
pub fn map<U>(self, f:impl FnOnce(T) -> U) -> Located<U> {
|
||||
Located::new(self.crumbs, f(self.item))
|
||||
}
|
||||
|
||||
/// Replaces the item, while pushing given crumbs on top of already present ones.
|
||||
pub fn into_descendant<U>(self, crumbs:Crumbs, item:U) -> Located<U> {
|
||||
let mut ret = self.map(|_| item);
|
||||
ret.crumbs.extend(crumbs);
|
||||
ret
|
||||
}
|
||||
|
||||
/// Maps into child, concatenating this crumbs and child crumbs.
|
||||
pub fn push_descendant<U>(self, child:Located<U>) -> Located<U> {
|
||||
self.into_descendant(child.crumbs,child.item)
|
||||
}
|
||||
}
|
||||
|
||||
/// Reference to AST stored under some known crumbs path.
|
||||
pub type ChildAst<'a> = Located<&'a Ast>;
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Tests ===
|
||||
// =============
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use utils::test::ExpectTuple;
|
||||
|
||||
/// Gets item under given crumb and checks if its representation is as expected.
|
||||
fn expect_repr<C:Crumbable>(item:&C, crumb:&C::Crumb, expected_repr:impl Str) {
|
||||
assert_eq!(item.get(crumb).unwrap().repr(), expected_repr.as_ref());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_crumb() {
|
||||
let lines = [
|
||||
Some(Ast::var("foo")),
|
||||
None,
|
||||
Some(Ast::var("bar"))
|
||||
];
|
||||
|
||||
let module = crate::Module::from_lines(&lines);
|
||||
|
||||
|
||||
// === Getting ===
|
||||
expect_repr(&module,&ModuleCrumb {line_index:0}, "foo");
|
||||
assert!(module.get(&ModuleCrumb {line_index:1}).is_err());
|
||||
expect_repr(&module,&ModuleCrumb {line_index:2}, "bar");
|
||||
assert!(module.get(&ModuleCrumb {line_index:3}).is_err());
|
||||
|
||||
let module2 = module.set(&ModuleCrumb {line_index:0}, Ast::var("foo2")).unwrap();
|
||||
assert_eq!(module2.repr(), "foo2\n\nbar");
|
||||
let module3 = module.set(&ModuleCrumb {line_index:1}, Ast::var("foo2")).unwrap();
|
||||
assert_eq!(module3.repr(), "foo\nfoo2\nbar");
|
||||
let module4 = module.set(&ModuleCrumb {line_index:3}, Ast::var("foo2"));
|
||||
assert!(module4.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_crumb() {
|
||||
let first_line = Ast::var("first_line");
|
||||
let tail_lines = [Some(Ast::var("tail0")), None, Some(Ast::var("tail2"))];
|
||||
let block = crate::Block::from_lines(&first_line,&tail_lines);
|
||||
|
||||
expect_repr(&block, &BlockCrumb::HeadLine, "first_line");
|
||||
expect_repr(&block, &BlockCrumb::TailLine {tail_index:0}, "tail0");
|
||||
assert!(block.get(&BlockCrumb::TailLine {tail_index:1}).is_err());
|
||||
expect_repr(&block, &BlockCrumb::TailLine {tail_index:2}, "tail2");
|
||||
assert!(block.get(&BlockCrumb::TailLine {tail_index:3}).is_err());
|
||||
|
||||
let block2 = block.set(&BlockCrumb::HeadLine, Ast::var("first_line2")).unwrap();
|
||||
assert_eq!(block2.repr(), "first_line2\ntail0\n\ntail2");
|
||||
let block3 = block.set(&BlockCrumb::TailLine {tail_index:1}, Ast::var("tail1")).unwrap();
|
||||
assert_eq!(block3.repr(), "first_line\ntail0\ntail1\ntail2");
|
||||
let block4 = block.set(&BlockCrumb::TailLine {tail_index:2}, Ast::var("tail22")).unwrap();
|
||||
assert_eq!(block4.repr(), "first_line\ntail0\n\ntail22");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infix_crumb() -> FallibleResult<()> {
|
||||
let infix = Ast::infix_var("foo","+","bar");
|
||||
let get = |infix_crumb| {
|
||||
let crumb = Crumb::Infix(infix_crumb);
|
||||
infix.get(&crumb)
|
||||
};
|
||||
let set = |infix_crumb, ast| {
|
||||
let crumb = Crumb::Infix(infix_crumb);
|
||||
infix.set(&crumb,ast)
|
||||
};
|
||||
let baz = Ast::var("baz");
|
||||
let times = Ast::opr("*");
|
||||
|
||||
|
||||
assert_eq!(infix.repr(), "foo + bar");
|
||||
|
||||
assert_eq!(get(InfixCrumb::LeftOperand)?.repr(), "foo");
|
||||
assert_eq!(get(InfixCrumb::Operator)?.repr(), "+");
|
||||
assert_eq!(get(InfixCrumb::RightOperand)?.repr(), "bar");
|
||||
|
||||
assert_eq!(set(InfixCrumb::LeftOperand, baz.clone())?.repr(), "baz + bar");
|
||||
assert_eq!(set(InfixCrumb::Operator, times.clone())?.repr(), "foo * bar");
|
||||
assert_eq!(set(InfixCrumb::RightOperand, baz.clone())?.repr(), "foo + baz");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_infix() -> FallibleResult<()> {
|
||||
use InfixCrumb::*;
|
||||
|
||||
let sum = Ast::infix_var("foo", "+", "bar");
|
||||
let infix = Ast::infix(Ast::var("main"), "=", sum);
|
||||
assert_eq!(infix.repr(), "main = foo + bar");
|
||||
|
||||
let set = |crumbs: &[InfixCrumb], ast| {
|
||||
let crumbs = crumbs.iter().map(|c| Crumb::Infix(*c)).collect_vec();
|
||||
infix.set_traversing(&crumbs, ast)
|
||||
};
|
||||
let get = |crumbs: &[InfixCrumb]| {
|
||||
let crumbs = crumbs.iter().map(|c| Crumb::Infix(*c)).collect_vec();
|
||||
infix.get_traversing(&crumbs)
|
||||
};
|
||||
|
||||
assert_eq!(set(&[RightOperand,LeftOperand], Ast::var("baz"))?.repr(), "main = baz + bar");
|
||||
assert_eq!(set(&[LeftOperand], Ast::var("baz"))?.repr(), "baz = foo + bar");
|
||||
|
||||
|
||||
assert_eq!(get(&[Operator])?.repr(), "=");
|
||||
assert_eq!(get(&[RightOperand])?.repr(), "foo + bar");
|
||||
assert_eq!(get(&[RightOperand,LeftOperand])?.repr(), "foo");
|
||||
assert_eq!(get(&[RightOperand,RightOperand])?.repr(), "bar");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn iterate_infix() {
|
||||
let sum = crate::Infix::from_vars("foo", "+", "bar");
|
||||
let (larg,opr,rarg) = sum.iter_subcrumbs().expect_tuple();
|
||||
assert_eq!(larg, InfixCrumb::LeftOperand);
|
||||
assert_eq!(opr, InfixCrumb::Operator);
|
||||
assert_eq!(rarg, InfixCrumb::RightOperand);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iterate_module() {
|
||||
let var = crate::Ast::var("foo");
|
||||
let lines = [
|
||||
Some(var.clone_ref()),
|
||||
None,
|
||||
Some(var.clone_ref()),
|
||||
];
|
||||
let module = crate::Module::from_lines(&lines);
|
||||
assert_eq!(module.repr(), "foo\n\nfoo");
|
||||
|
||||
let (line0,line2) = module.iter_subcrumbs().expect_tuple();
|
||||
assert_eq!(line0.line_index,0);
|
||||
assert_eq!(line2.line_index,2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iterate_block() {
|
||||
let first_line = crate::Ast::var("foo");
|
||||
let lines = [
|
||||
Some(crate::Ast::var("bar")),
|
||||
None,
|
||||
Some(crate::Ast::var("baz")),
|
||||
];
|
||||
let block = crate::Block::from_lines(&first_line,&lines);
|
||||
let (line0,line1,line3) = block.iter_subcrumbs().expect_tuple();
|
||||
assert_eq!(line0, BlockCrumb::HeadLine);
|
||||
assert_eq!(line1, BlockCrumb::TailLine {tail_index:0});
|
||||
assert_eq!(line3, BlockCrumb::TailLine {tail_index:2});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mismatched_crumb() {
|
||||
let sum = Ast::infix_var("foo", "+", "bar");
|
||||
let crumb = Crumb::Module(ModuleCrumb {line_index:0});
|
||||
let first_line = sum.get(&crumb);
|
||||
first_line.expect_err("Using module crumb on infix should fail");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn located() {
|
||||
let item = Located::new_root("zero");
|
||||
assert_eq!(item.item, "zero");
|
||||
assert!(item.crumbs.is_empty());
|
||||
|
||||
let item = item.into_descendant(vec![Crumb::Infix(InfixCrumb::LeftOperand)], 1);
|
||||
assert_eq!(item.item, 1);
|
||||
let (crumb0,) = item.crumbs.iter().expect_tuple();
|
||||
assert_eq!(crumb0,&Crumb::Infix(InfixCrumb::LeftOperand));
|
||||
|
||||
let child_item = Located::new_direct_child(InfixCrumb::Operator, "two");
|
||||
let item = item.push_descendant(child_item);
|
||||
assert_eq!(item.item, "two");
|
||||
let (crumb0,crumb1) = item.crumbs.iter().expect_tuple();
|
||||
assert_eq!(crumb0,&Crumb::Infix(InfixCrumb::LeftOperand));
|
||||
assert_eq!(crumb1,&Crumb::Infix(InfixCrumb::Operator));
|
||||
|
||||
let item2 = item.clone().map(|item| item.len() );
|
||||
assert_eq!(item2.item,3);
|
||||
assert_eq!(item.crumbs,item2.crumbs);
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,45 @@ impl<T> KnownAst<T> {
|
||||
Ok(KnownAst {ast,phantom:default()})
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `KnownAst<T>` from ast node containing shape of variant `T`.
|
||||
///
|
||||
/// Note that this API requires caller to ensure that Ast stores proper shape. Violating this
|
||||
/// rule will lead to panics later.
|
||||
fn new_unchecked(ast:Ast) -> KnownAst<T> {
|
||||
KnownAst {ast,phantom:default()}
|
||||
}
|
||||
|
||||
/// Gets AST id.
|
||||
pub fn id(&self) -> Option<crate::Id> { self.ast.id }
|
||||
|
||||
/// Returns a reference to the stored `Ast` with `Shape` of `T`.
|
||||
pub fn ast(&self) -> &Ast { &self.ast }
|
||||
|
||||
/// Returns the AST's shape.
|
||||
pub fn shape<E>(&self) -> &T
|
||||
where for<'t> &'t Shape<Ast> : TryInto<&'t T,Error=E>,
|
||||
E : Debug, {
|
||||
self.deref()
|
||||
}
|
||||
|
||||
/// Updated self in place by applying given function on the stored Shape.
|
||||
pub fn update_shape<E>(&mut self, f:impl FnOnce(&mut T))
|
||||
where for<'t> &'t Shape<Ast> : TryInto<&'t T,Error=E>,
|
||||
T : Clone + Into<Shape<Ast>>,
|
||||
E : Debug {
|
||||
let mut shape = self.shape().clone();
|
||||
f(&mut shape);
|
||||
self.ast = self.ast.with_shape(shape)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T:Into<Shape<Ast>>> KnownAst<T> {
|
||||
/// Creates a new `KnownAst<T>` from `shape`.
|
||||
pub fn new(shape:T, id:Option<crate::Id>) -> KnownAst<T> {
|
||||
let ast = Ast::new(shape,id);
|
||||
Self::new_unchecked(ast)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T,E> Deref for KnownAst<T>
|
||||
@ -74,6 +113,13 @@ impl<T> From<KnownAst<T>> for Ast {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a,T> From<&'a KnownAst<T>> for &'a Ast {
|
||||
fn from(known_ast:&'a KnownAst<T>) -> &'a Ast {
|
||||
&known_ast.ast
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
|
@ -7,6 +7,8 @@
|
||||
#[warn(missing_docs)]
|
||||
pub mod assoc;
|
||||
#[warn(missing_docs)]
|
||||
pub mod crumbs;
|
||||
#[warn(missing_docs)]
|
||||
pub mod internal;
|
||||
#[warn(missing_docs)]
|
||||
pub mod known;
|
||||
@ -18,8 +20,18 @@ pub mod prefix;
|
||||
pub mod repr;
|
||||
#[warn(missing_docs)]
|
||||
pub mod test_utils;
|
||||
#[warn(missing_docs)]
|
||||
pub mod traits;
|
||||
|
||||
use prelude::*;
|
||||
|
||||
pub mod prelude {
|
||||
pub use enso_prelude::*;
|
||||
|
||||
pub use crate::Ast;
|
||||
pub use crate::traits::*;
|
||||
}
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use ast_macros::*;
|
||||
use data::text::Index;
|
||||
@ -38,16 +50,16 @@ use uuid::Uuid;
|
||||
/// A mapping between text position and immutable ID.
|
||||
#[derive(Clone,Debug,Default,Deserialize,Eq,PartialEq,Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct IdMap{ pub vec:Vec<(Span,ID)> }
|
||||
pub struct IdMap{ pub vec:Vec<(Span,Id)> }
|
||||
|
||||
impl IdMap {
|
||||
/// Create a new instance.
|
||||
pub fn new(vec:Vec<(Span,ID)>) -> IdMap {
|
||||
pub fn new(vec:Vec<(Span,Id)>) -> IdMap {
|
||||
IdMap {vec}
|
||||
}
|
||||
/// Assigns Span to given ID.
|
||||
pub fn insert(&mut self, span:Span, id:ID) {
|
||||
self.vec.push((span, id));
|
||||
pub fn insert(&mut self, span:Span, id:Id) {
|
||||
self.vec.push((span,id));
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,7 +75,7 @@ pub type Stream<T> = Vec<T>;
|
||||
/// Exception raised by macro-generated TryFrom methods that try to "downcast"
|
||||
/// enum type to its variant subtype if different constructor was used.
|
||||
#[derive(Display, Debug, Fail)]
|
||||
pub struct WrongEnum { pub expected_con: String }
|
||||
pub struct WrongEnum {pub expected_con:String}
|
||||
|
||||
|
||||
|
||||
@ -78,7 +90,7 @@ pub struct WrongEnum { pub expected_con: String }
|
||||
#[derive(Clone,Eq,PartialEq,Debug,Serialize,Deserialize)]
|
||||
pub struct Tree<K,V> {
|
||||
pub value : Option<V>,
|
||||
pub branches : Vec<(K, Tree<K,V>)>,
|
||||
pub branches : Vec<(K,Tree<K,V>)>,
|
||||
}
|
||||
|
||||
|
||||
@ -209,14 +221,14 @@ impl Ast {
|
||||
|
||||
/// Wraps given shape with an optional ID into Ast.
|
||||
/// Length will ba automatically calculated based on Shape.
|
||||
pub fn new<S:Into<Shape<Ast>>>(shape:S, id:Option<ID>) -> Ast {
|
||||
pub fn new<S:Into<Shape<Ast>>>(shape:S, id:Option<Id>) -> Ast {
|
||||
let shape: Shape<Ast> = shape.into();
|
||||
let length = shape.len();
|
||||
Ast::new_with_length(shape,id,length)
|
||||
}
|
||||
|
||||
/// Just wraps shape, id and len into Ast node.
|
||||
pub fn from_ast_id_len(shape:Shape<Ast>, id:Option<ID>, len:usize) -> Ast {
|
||||
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) }
|
||||
@ -224,7 +236,7 @@ impl Ast {
|
||||
|
||||
/// 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 {
|
||||
(shape:S, id:Option<Id>, len:usize) -> Ast {
|
||||
let shape = shape.into();
|
||||
Self::from_ast_id_len(shape,id,len)
|
||||
}
|
||||
@ -235,10 +247,20 @@ impl Ast {
|
||||
}
|
||||
|
||||
/// Returns this AST node with ID set to given value.
|
||||
pub fn with_id(&self, id:ID) -> Ast {
|
||||
pub fn with_id(&self, id:Id) -> Ast {
|
||||
Ast::from_ast_id_len(self.shape().clone(), Some(id), self.len())
|
||||
}
|
||||
|
||||
/// Returns this AST node with a newly generated unique ID.
|
||||
pub fn with_new_id(&self) -> Ast {
|
||||
self.with_id(Id::new_v4())
|
||||
}
|
||||
|
||||
/// Returns this AST node with shape set to given value.
|
||||
pub fn with_shape<S:Into<Shape<Ast>>>(&self, shape:S) -> Ast {
|
||||
Ast::new(shape.into(),self.id)
|
||||
}
|
||||
|
||||
/// Returns this AST node with removed ID.
|
||||
pub fn without_id(&self) -> Ast {
|
||||
Ast::from_ast_id_len(self.shape().clone(), None, self.len())
|
||||
@ -299,7 +321,7 @@ impl<'de> Visitor<'de> for AstDeserializationVisitor {
|
||||
use ast_schema::*;
|
||||
|
||||
let mut shape: Option<Shape<Ast>> = None;
|
||||
let mut id: Option<Option<ID>> = None;
|
||||
let mut id: Option<Option<Id>> = None;
|
||||
let mut len: Option<usize> = None;
|
||||
|
||||
while let Some(key) = map.next_key()? {
|
||||
@ -374,13 +396,26 @@ pub enum Shape<T> {
|
||||
SectionSides { opr : T },
|
||||
|
||||
// === Module ===
|
||||
|
||||
/// Module represent the file's root block: sequence of possibly empty lines with no leading
|
||||
/// indentation.
|
||||
Module { lines : Vec<BlockLine<Option<T>>> },
|
||||
Block { ty : BlockType
|
||||
, indent : usize
|
||||
, empty_lines : Vec<usize>
|
||||
, first_line : BlockLine<T>
|
||||
, lines : Vec<BlockLine<Option<T>>>
|
||||
, is_orphan : bool },
|
||||
/// Block is the sequence of equally indented lines. Lines may contain some child `T` or be
|
||||
/// empty. Block is used for all code blocks except for the root one, which uses `Module`.
|
||||
Block { /// Type of Block, depending on whether it is introduced by an operator.
|
||||
/// Note [mwu] Doesn't really do anything right now, likely to be removed.
|
||||
ty : BlockType,
|
||||
/// Absolute's block indent, counting from the module's root.
|
||||
indent : usize,
|
||||
/// Leading empty lines. Each line is represented by absolute count of spaces
|
||||
/// it contains, counting from the root.
|
||||
empty_lines : Vec<usize>,
|
||||
/// First line with non-empty item.
|
||||
first_line : BlockLine<T>,
|
||||
/// Rest of lines, each of them optionally having contents.
|
||||
lines : Vec<BlockLine<Option<T>>>,
|
||||
/// Does the Block start with a leading newline.
|
||||
is_orphan : bool },
|
||||
|
||||
// === Macros ===
|
||||
Match { pfx : Option<MacroPatternMatch<Shifted<Ast>>>
|
||||
@ -414,6 +449,8 @@ macro_rules! with_shape_variants {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
// === Builder ===
|
||||
// ===============
|
||||
@ -501,8 +538,16 @@ pub enum Escape {
|
||||
// === Block ===
|
||||
// =============
|
||||
|
||||
#[ast_node] pub enum BlockType { Continuous { } , Discontinuous { } }
|
||||
#[ast] pub struct BlockLine <T> { pub elem: T, pub off: usize }
|
||||
#[ast_node] pub enum BlockType {Continuous {} , Discontinuous {}}
|
||||
|
||||
/// Holder for line in `Block` or `Module`. Lines store value of `T` and trailing whitespace info.
|
||||
#[ast]
|
||||
pub struct BlockLine <T> {
|
||||
/// The AST stored in the line.
|
||||
pub elem: T,
|
||||
/// The trailing whitespace in the line after the `elem`.
|
||||
pub off: usize
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -852,10 +897,10 @@ impl<T:HasTokens> HasRepr for T {
|
||||
|
||||
// === WithID ===
|
||||
|
||||
pub type ID = Uuid;
|
||||
pub type Id = Uuid;
|
||||
|
||||
pub trait HasID {
|
||||
fn id(&self) -> Option<ID>;
|
||||
fn id(&self) -> Option<Id>;
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Shrinkwrap, Serialize, Deserialize)]
|
||||
@ -864,12 +909,12 @@ pub struct WithID<T> {
|
||||
#[shrinkwrap(main_field)]
|
||||
#[serde(flatten)]
|
||||
pub wrapped: T,
|
||||
pub id: Option<ID>
|
||||
pub id: Option<Id>
|
||||
}
|
||||
|
||||
impl<T> HasID for WithID<T>
|
||||
where T: HasID {
|
||||
fn id(&self) -> Option<ID> {
|
||||
fn id(&self) -> Option<Id> {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
@ -918,7 +963,7 @@ where T: HasLength + Into<S> {
|
||||
|
||||
impl<T> HasID for WithLength<T>
|
||||
where T: HasID {
|
||||
fn id(&self) -> Option<ID> {
|
||||
fn id(&self) -> Option<Id> {
|
||||
self.deref().id()
|
||||
}
|
||||
}
|
||||
@ -930,6 +975,74 @@ impl<T> HasID for WithLength<T>
|
||||
// TODO: the definitions below should be removed and instead generated using
|
||||
// macros, as part of https://github.com/luna/enso/issues/338
|
||||
|
||||
// === Shape ===
|
||||
|
||||
impl<T> BlockLine<T> {
|
||||
/// Creates a new BlockLine wrapping given item and having 0 offset.
|
||||
pub fn new(elem:T) -> BlockLine<T> {
|
||||
BlockLine {elem,off:0}
|
||||
}
|
||||
}
|
||||
|
||||
impl <T> Block<T> {
|
||||
/// Concatenate `Block`'s `first_line` with `lines` and returns a collection with all the lines.
|
||||
pub fn all_lines(&self) -> Vec<BlockLine<Option<T>>> where T:Clone {
|
||||
let mut lines = Vec::new();
|
||||
for off in &self.empty_lines {
|
||||
let elem = None;
|
||||
// TODO [mwu]
|
||||
// Empty lines use absolute indent, while BlockLines are relative to Block.
|
||||
// We might lose some data here, as empty lines shorter than block will get filled
|
||||
// with spaces. This is something that should be improved in the future but also
|
||||
// requires changes in the AST.
|
||||
let off = off.checked_sub(self.indent).unwrap_or(0);
|
||||
lines.push(BlockLine{elem,off})
|
||||
}
|
||||
|
||||
let first_line = self.first_line.clone();
|
||||
let elem = Some(first_line.elem);
|
||||
let off = first_line.off;
|
||||
lines.push(BlockLine{elem,off});
|
||||
lines.extend(self.lines.iter().cloned());
|
||||
lines
|
||||
}
|
||||
}
|
||||
|
||||
impl Block<Ast> {
|
||||
/// Creates block from given line ASTs. There is no leading AST (it is orphan block).
|
||||
pub fn from_lines(first_line:&Ast, tail_lines:&[Option<Ast>]) -> Block<Ast> {
|
||||
let ty = BlockType::Discontinuous {};
|
||||
let indent = 0;
|
||||
let empty_lines = Vec::new();
|
||||
let first_line = BlockLine::new(first_line.clone_ref());
|
||||
let lines = tail_lines.iter().cloned().map(BlockLine::new).collect();
|
||||
let is_orphan = true;
|
||||
Block {ty,indent,empty_lines,first_line,lines,is_orphan}
|
||||
}
|
||||
}
|
||||
|
||||
impl Infix<Ast> {
|
||||
/// Creates an `Infix` Shape, where both its operands are Vars and spacing is 1.
|
||||
pub fn from_vars<Str0,Str1,Str2>(larg:Str0, opr:Str1, rarg:Str2) -> Infix<Ast>
|
||||
where Str0 : ToString,
|
||||
Str1 : ToString,
|
||||
Str2 : ToString, {
|
||||
let larg = Ast::var(larg);
|
||||
let loff = 1;
|
||||
let opr = Ast::opr(opr);
|
||||
let roff = 1;
|
||||
let rarg = Ast::var(rarg);
|
||||
Infix {larg,loff,opr,roff,rarg}
|
||||
}
|
||||
}
|
||||
|
||||
impl Module<Ast> {
|
||||
/// Creates a `Module` Shape with lines storing given Asts and having 0 offset.
|
||||
pub fn from_lines(line_asts:&[Option<Ast>]) -> Module<Ast> {
|
||||
let lines = line_asts.iter().cloned().map(|elem| BlockLine {elem, off:0}).collect();
|
||||
Module {lines}
|
||||
}
|
||||
}
|
||||
|
||||
// === AST ===
|
||||
|
||||
@ -950,7 +1063,7 @@ impl Ast {
|
||||
}
|
||||
|
||||
/// Creates an Ast node with Var inside and given ID.
|
||||
pub fn var_with_id<Name:Str>(name:Name, id:ID) -> Ast {
|
||||
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))
|
||||
@ -972,17 +1085,23 @@ impl Ast {
|
||||
Ast::from(opr)
|
||||
}
|
||||
|
||||
/// Creates an AST node with `Infix` shape.
|
||||
pub fn infix(larg:impl Into<Ast>, opr:impl Str, rarg:impl Into<Ast>) -> Ast {
|
||||
let larg = larg.into();
|
||||
let loff = 1;
|
||||
let opr = Ast::opr(opr.into());
|
||||
let roff = 1;
|
||||
let rarg = rarg.into();
|
||||
let infix = Infix {larg,loff,opr,roff,rarg};
|
||||
Ast::from(infix)
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
let larg = Ast::var(larg);
|
||||
let loff = 1;
|
||||
let opr = Ast::opr(opr);
|
||||
let roff = 1;
|
||||
let rarg = Ast::var(rarg);
|
||||
let infix = Infix { larg, loff, opr, roff, rarg };
|
||||
let infix = Infix::from_vars(larg,opr,rarg);
|
||||
Ast::from(infix)
|
||||
}
|
||||
}
|
||||
@ -1106,6 +1225,8 @@ mod tests {
|
||||
use data::text::Size;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use utils::test::ExpectTuple;
|
||||
|
||||
/// Assert that given value round trips JSON serialization.
|
||||
fn round_trips<T>(input_val: &T)
|
||||
where T: Serialize + DeserializeOwned + PartialEq + Debug {
|
||||
@ -1242,4 +1363,34 @@ mod tests {
|
||||
assert_eq!((&abc).iter().count(), 2); // for App's two children
|
||||
assert_eq!(abc.iter_recursive().count(), 5); // for 2 Apps and 3 Vars
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_lines_of_block() {
|
||||
let ty = BlockType::Discontinuous {};
|
||||
let indent = 4;
|
||||
let empty_lines = vec![5];
|
||||
let first_line = BlockLine {elem:Ast::var("head"), off:3};
|
||||
let lines = vec![
|
||||
BlockLine {elem:Some(Ast::var("tail0")), off:2},
|
||||
BlockLine {elem:None, off:1},
|
||||
BlockLine {elem:Some(Ast::var("tail2")), off:3},
|
||||
];
|
||||
let is_orphan = false;
|
||||
let block = Block {ty,indent,empty_lines,first_line,lines,is_orphan};
|
||||
let expected_repr = "\n \n head \n tail0 \n \n tail2 ";
|
||||
assert_eq!(block.repr(), expected_repr);
|
||||
|
||||
let all_lines = block.all_lines();
|
||||
let (empty_line,head_line,tail0,tail1,tail2) = all_lines.iter().expect_tuple();
|
||||
assert!(empty_line.elem.is_none());
|
||||
assert_eq!(empty_line.off,1); // other 4 indents are provided by Block
|
||||
assert_eq!(head_line.elem.as_ref().unwrap().repr(),"head");
|
||||
assert_eq!(head_line.off,3);
|
||||
assert_eq!(tail0.elem.as_ref().unwrap().repr(),"tail0");
|
||||
assert_eq!(tail0.off,2);
|
||||
assert!(tail1.elem.is_none());
|
||||
assert_eq!(tail1.off,1);
|
||||
assert_eq!(tail2.elem.as_ref().unwrap().repr(),"tail2");
|
||||
assert_eq!(tail2.off,3);
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,12 @@ pub fn to_assignment(ast:&Ast) -> Option<known::Infix> {
|
||||
is_assignment_opr(&infix.opr).then(infix)
|
||||
}
|
||||
|
||||
/// Checks if a given node is an assignment infix expression.
|
||||
pub fn is_assignment(ast:&Ast) -> bool {
|
||||
let infix = known::Infix::try_from(ast);
|
||||
infix.map(|infix| is_assignment_opr(&infix.opr)).unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Infix operator operand. Optional, as we deal with Section* nodes as well.
|
||||
pub type Operand = Option<Ast>;
|
||||
|
||||
|
@ -263,6 +263,8 @@ impl <T:HasTokens> HasTokens for TextUnclosed<T> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ====================
|
||||
// === Applications ===
|
||||
// ====================
|
||||
@ -334,6 +336,7 @@ impl<T:HasTokens> HasTokens for Match<T> {
|
||||
has_tokens!(Ambiguous, self.segs);
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === Spaceless AST ===
|
||||
// =====================
|
||||
|
@ -3,6 +3,8 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::Ast;
|
||||
use crate::HasLength;
|
||||
use crate::HasRepr;
|
||||
use crate::Shape;
|
||||
use crate::Module;
|
||||
|
||||
@ -20,10 +22,21 @@ pub fn expect_shape<'t,T>(ast:&'t Ast) -> &'t T
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes Ast being a module with a single line and returns that line's AST.
|
||||
/// Takes Ast being a module with a single non-empty line and returns that line's AST.
|
||||
/// Panics, if this is not a module or if it does not have exactly one line.
|
||||
pub fn expect_single_line(ast:&Ast) -> &Ast {
|
||||
let module:&Module<Ast> = expect_shape(ast);
|
||||
let (line,) = (&module.lines).expect_tuple();
|
||||
line.elem.as_ref().unwrap()
|
||||
let (line,) = (module.iter()).expect_tuple();
|
||||
line
|
||||
}
|
||||
|
||||
/// Checks if all nodes in subtree have declared spans equal to
|
||||
/// spans we calculate.
|
||||
pub fn validate_spans(ast:&Ast) {
|
||||
for node in ast.iter_recursive() {
|
||||
let calculated = node.shape().len();
|
||||
let declared = node.wrapped.wrapped.len;
|
||||
assert_eq!(calculated, declared
|
||||
, "`{}` part of `{}`", node.repr(), ast.repr());
|
||||
}
|
||||
}
|
||||
|
7
gui/src/rust/ide/ast/impl/src/traits.rs
Normal file
7
gui/src/rust/ide/ast/impl/src/traits.rs
Normal file
@ -0,0 +1,7 @@
|
||||
//! Common traits defined by this crate.
|
||||
|
||||
pub use crate::crumbs::Crumbable;
|
||||
pub use crate::crumbs::TraversableAst;
|
||||
pub use crate::HasID;
|
||||
pub use crate::HasLength;
|
||||
pub use crate::HasRepr;
|
@ -11,7 +11,7 @@ pub use ast::Ast;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use utils::fail::FallibleResult;
|
||||
|
||||
|
||||
// ================
|
||||
@ -63,11 +63,30 @@ pub trait IsParser : Debug {
|
||||
fn parse(&mut self, program:String, ids:IdMap) -> Result<Ast>;
|
||||
|
||||
/// Parse program into module.
|
||||
fn parse_module(&mut self, program:String, ids:IdMap) -> Result<ast::known::Module> {
|
||||
let ast = self.parse(program,ids)?;
|
||||
fn parse_module(&mut self, program:impl Str, ids:IdMap) -> Result<ast::known::Module> {
|
||||
let ast = self.parse(program.into(),ids)?;
|
||||
ast::known::Module::try_from(ast).map_err(|_| Error::NonModuleRoot)
|
||||
}
|
||||
|
||||
/// Program is expected to be single non-empty line module. The line's AST is
|
||||
/// returned. Panics otherwise.
|
||||
fn parse_line(&mut self, program:impl Str) -> FallibleResult<Ast> {
|
||||
let module = self.parse_module(program,default())?;
|
||||
|
||||
let mut lines = module.lines.clone().into_iter().filter_map(|line| {
|
||||
line.elem
|
||||
});
|
||||
if let Some(first_non_empty_line) = lines.next() {
|
||||
if lines.next().is_some() {
|
||||
Err(TooManyLinesProduced.into())
|
||||
} else {
|
||||
Ok(first_non_empty_line)
|
||||
}
|
||||
} else {
|
||||
Err(NoLinesProduced.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse contents of the program source file,
|
||||
/// where program code may be followed by idmap and metadata.
|
||||
fn parse_with_metadata<M:Metadata>
|
||||
@ -97,6 +116,16 @@ pub enum Error {
|
||||
InteropError(#[cause] Box<dyn Fail>),
|
||||
}
|
||||
|
||||
/// When trying to parse a line, not a single line was produced.
|
||||
#[derive(Debug,Fail,Clone,Copy)]
|
||||
#[fail(display = "Expected a single line, parsed none.")]
|
||||
struct NoLinesProduced;
|
||||
|
||||
/// When trying to parse a single line, more were generated.
|
||||
#[derive(Debug,Fail,Clone,Copy)]
|
||||
#[fail(display = "Expected just a single line, found more.")]
|
||||
struct TooManyLinesProduced;
|
||||
|
||||
/// Wraps an arbitrary `std::error::Error` as an `InteropError.`
|
||||
pub fn interop_error<T>(error:T) -> Error
|
||||
where T: Fail {
|
||||
|
@ -15,6 +15,8 @@
|
||||
#![warn(missing_debug_implementations)]
|
||||
|
||||
pub mod api;
|
||||
pub mod test_utils;
|
||||
|
||||
mod jsclient;
|
||||
mod wsclient;
|
||||
|
||||
|
40
gui/src/rust/ide/parser/src/test_utils.rs
Normal file
40
gui/src/rust/ide/parser/src/test_utils.rs
Normal file
@ -0,0 +1,40 @@
|
||||
//! Utilities for writing tests using parser. Should not be used in production parts.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::api::IsParser;
|
||||
|
||||
use ast::Ast;
|
||||
use ast::Shape;
|
||||
use ast::HasLength;
|
||||
use ast::HasRepr;
|
||||
use ast::test_utils::expect_single_line;
|
||||
use ast::test_utils::expect_shape;
|
||||
use ast::test_utils::validate_spans;
|
||||
|
||||
/// Additional methods for parser to ease writing tests.
|
||||
pub trait ParserTestExts : IsParser {
|
||||
/// Program is expected to be a module with a single non-emty line. Its AST
|
||||
/// is reinterpret as given `Shape`.
|
||||
fn parse_shape<T>(&mut self, program:impl Str) -> T
|
||||
where for<'t> &'t Shape<Ast>: TryInto<&'t T>,
|
||||
T : Clone + 'static {
|
||||
let ast = self.parse_testing(program);
|
||||
let line = expect_single_line(&ast);
|
||||
let shape = expect_shape(line);
|
||||
shape.clone()
|
||||
}
|
||||
|
||||
/// Runs parser on given input, panics on any error.
|
||||
fn parse_testing(&mut self, program:impl Str) -> Ast {
|
||||
let program = program.into();
|
||||
println!("parsing {}", program);
|
||||
let ast = self.parse(program.clone(), default()).unwrap();
|
||||
assert_eq!(ast.shape().len(), program.len());
|
||||
validate_spans(&ast);
|
||||
assert_eq!(ast.repr(), program, "{:?}", ast);
|
||||
ast
|
||||
}
|
||||
}
|
||||
|
||||
impl<T:IsParser> ParserTestExts for T {}
|
@ -4,7 +4,6 @@ use parser::prelude::*;
|
||||
|
||||
use ast::*;
|
||||
use ast::test_utils::expect_shape;
|
||||
use ast::test_utils::expect_single_line;
|
||||
use parser::api::IsParser;
|
||||
use parser::api::SourceFile;
|
||||
use utils::test::ExpectTuple;
|
||||
@ -29,17 +28,6 @@ fn assert_opr<StringLike:Into<String>>(ast:&Ast, name:StringLike) {
|
||||
assert_eq!(*actual,expected);
|
||||
}
|
||||
|
||||
/// Checks if all nodes in subtree have declared spans equal to
|
||||
/// spans we calculate.
|
||||
fn validate_spans(ast:&Ast) {
|
||||
for node in ast.iter_recursive() {
|
||||
let calculated = node.shape().len();
|
||||
let declared = node.wrapped.wrapped.len;
|
||||
assert_eq!(calculated, declared
|
||||
, "`{}` part of `{}`", node.repr(), ast.repr());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
@ -60,30 +48,12 @@ impl Fixture {
|
||||
Fixture {parser:parser::Parser::new_or_panic()}
|
||||
}
|
||||
|
||||
/// Runs parser on given input, panics on any error.
|
||||
fn parse(&mut self, program:&str) -> Ast {
|
||||
println!("parsing {}", program);
|
||||
let ast = self.parser.parse(program.into(), default()).unwrap();
|
||||
assert_eq!(ast.shape().len(), program.len());
|
||||
validate_spans(&ast);
|
||||
assert_eq!(ast.repr(), program, "{:?}", ast);
|
||||
ast
|
||||
}
|
||||
|
||||
/// Program is expected to be single line module. The line's AST is
|
||||
/// returned. Panics otherwise.
|
||||
fn parse_line(&mut self, program:&str) -> Ast {
|
||||
let ast = self.parse(program);
|
||||
let line = expect_single_line(&ast);
|
||||
line.clone()
|
||||
}
|
||||
|
||||
/// Program is expected to be single line module. The line's Shape subtype
|
||||
/// is obtained and passed to `tester`.
|
||||
fn test_shape<T,F>(&mut self, program:&str, tester:F)
|
||||
where for<'t> &'t Shape<Ast>: TryInto<&'t T>,
|
||||
F : FnOnce(&T) -> () {
|
||||
let ast = self.parse_line(program);
|
||||
let ast = self.parser.parse_line(program).unwrap();
|
||||
let shape = expect_shape(&ast);
|
||||
tester(shape);
|
||||
}
|
||||
@ -380,7 +350,7 @@ impl Fixture {
|
||||
];
|
||||
|
||||
for macro_usage in macro_usages.iter() {
|
||||
let ast = self.parse_line(macro_usage);
|
||||
let ast = self.parser.parse_line(*macro_usage).unwrap();
|
||||
expect_shape::<Match<Ast>>(&ast);
|
||||
};
|
||||
}
|
||||
|
@ -7,11 +7,17 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub use crate::double_representation::graph::Id;
|
||||
pub use crate::double_representation::graph::LocationHint;
|
||||
use crate::controller::module::NodeMetadata;
|
||||
use crate::double_representation::graph::GraphInfo;
|
||||
use crate::double_representation::definition;
|
||||
use crate::double_representation::node;
|
||||
|
||||
use flo_stream::MessagePublisher;
|
||||
use flo_stream::Subscriber;
|
||||
use utils::channel::process_stream_with_handle;
|
||||
use parser::api::IsParser;
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
@ -21,7 +27,17 @@ use utils::channel::process_stream_with_handle;
|
||||
/// 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);
|
||||
pub struct NodeNotFound(ast::Id);
|
||||
|
||||
/// Error raised when an attempt to set node's expression to a binding has been made.
|
||||
#[derive(Clone,Debug,Fail)]
|
||||
#[fail(display="Illegal string `{}` given for node expression. It must not be a binding.", _0)]
|
||||
pub struct BindingExpressionNotAllowed(String);
|
||||
|
||||
/// Expression AST cannot be used to produce a node. Means a bug in parser and id-giving code.
|
||||
#[derive(Clone,Copy,Debug,Fail)]
|
||||
#[fail(display="Internal error: failed to create a new node.")]
|
||||
pub struct FailedToCreateNode;
|
||||
|
||||
|
||||
|
||||
@ -52,22 +68,21 @@ pub struct NewNodeInfo {
|
||||
/// Visual node position in the graph scene.
|
||||
pub metadata : Option<NodeMetadata>,
|
||||
/// ID to be given to the node.
|
||||
pub id : Option<ast::ID>,
|
||||
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,
|
||||
impl NewNodeInfo {
|
||||
/// New node with given expression added at the end of the graph's blocks.
|
||||
pub fn new_pushed_back(expression:impl Str) -> NewNodeInfo {
|
||||
NewNodeInfo {
|
||||
expression : expression.into(),
|
||||
metadata : default(),
|
||||
id : default(),
|
||||
location_hint : LocationHint::End,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -86,6 +101,7 @@ pub struct Handle {
|
||||
/// 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>>>,
|
||||
logger : Logger
|
||||
}
|
||||
|
||||
impl Handle {
|
||||
@ -105,7 +121,8 @@ impl Handle {
|
||||
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 logger = Logger::new(format!("Graph Controller {}", id));
|
||||
let ret = Handle {module,id,publisher,logger};
|
||||
let weak = Rc::downgrade(&ret.publisher);
|
||||
let relay_notifications = process_stream_with_handle(graphs_notifications,weak,
|
||||
|notification,this| {
|
||||
@ -142,12 +159,12 @@ impl Handle {
|
||||
(&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)
|
||||
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> {
|
||||
(&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())
|
||||
@ -157,7 +174,7 @@ impl Handle {
|
||||
///
|
||||
/// 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> {
|
||||
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})
|
||||
@ -174,15 +191,80 @@ impl Handle {
|
||||
Ok(nodes)
|
||||
}
|
||||
|
||||
/// Updates the AST of the definition of this graph.
|
||||
pub fn update_definition_ast<F>(&self, f:F) -> FallibleResult<()>
|
||||
where F:FnOnce(definition::DefinitionInfo) -> FallibleResult<definition::DefinitionInfo> {
|
||||
let ast_so_far = self.module.ast()?;
|
||||
let definition = definition::locate(&ast_so_far, &self.id)?;
|
||||
let new_definition = f(definition.item)?;
|
||||
trace!(self.logger, "Applying graph changes onto definition");
|
||||
let new_ast = new_definition.ast.into();
|
||||
let new_module = ast_so_far.set_traversing(&definition.crumbs,new_ast)?;
|
||||
self.module.update_ast(new_module);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses given text as a node expression.
|
||||
pub fn parse_node_expression
|
||||
(&self, expression_text:impl Str) -> FallibleResult<Ast> {
|
||||
let mut parser = self.module.parser();
|
||||
let node_ast = parser.parse_line(expression_text.as_ref())?;
|
||||
if ast::opr::is_assignment(&node_ast) {
|
||||
Err(BindingExpressionNotAllowed(expression_text.into()).into())
|
||||
} else {
|
||||
Ok(node_ast)
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a new node to the graph and returns information about created node.
|
||||
pub fn add_node(&self, _node:NewNodeInfo) -> FallibleResult<Node> {
|
||||
todo!()
|
||||
pub fn add_node(&self, node:NewNodeInfo) -> FallibleResult<ast::Id> {
|
||||
trace!(self.logger, "Adding node with expression `{node.expression}`");
|
||||
let ast = self.parse_node_expression(&node.expression)?;
|
||||
let mut node_info = node::NodeInfo::from_line_ast(&ast).ok_or(FailedToCreateNode)?;
|
||||
if let Some(desired_id) = node.id {
|
||||
node_info.set_id(desired_id)
|
||||
}
|
||||
|
||||
self.update_definition_ast(|definition| {
|
||||
let mut graph = GraphInfo::from_definition(definition);
|
||||
let node_ast = node_info.ast().clone();
|
||||
graph.add_node(node_ast,node.location_hint)?;
|
||||
Ok(graph.source)
|
||||
})?;
|
||||
|
||||
if let Some(initial_metadata) = node.metadata {
|
||||
self.with_node_metadata(node_info.id(),|metadata| {
|
||||
*metadata = initial_metadata;
|
||||
})
|
||||
}
|
||||
|
||||
Ok(node_info.id())
|
||||
}
|
||||
|
||||
/// Removes the node with given Id.
|
||||
pub fn remove_node(&self, id:ast::ID) -> FallibleResult<()> {
|
||||
self.module().pop_node_metadata(id)?;
|
||||
todo!()
|
||||
pub fn remove_node(&self, id:ast::Id) -> FallibleResult<()> {
|
||||
trace!(self.logger, "Removing node {id}");
|
||||
self.update_definition_ast(|definition| {
|
||||
let mut graph = GraphInfo::from_definition(definition);
|
||||
graph.remove_node(id)?;
|
||||
Ok(graph.source)
|
||||
})?;
|
||||
|
||||
// It's fine if there were no metadata.
|
||||
let _ = self.module().pop_node_metadata(id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the given's node expression.
|
||||
pub fn set_expression(&self, id:ast::Id, expression_text:impl Str) -> FallibleResult<()> {
|
||||
trace!(self.logger, "Setting node {id} expression to `{expression_text.as_ref()}`");
|
||||
let new_expression_ast = self.parse_node_expression(expression_text)?;
|
||||
self.update_definition_ast(|definition| {
|
||||
let mut graph = GraphInfo::from_definition(definition);
|
||||
graph.edit_node(id,new_expression_ast)?;
|
||||
Ok(graph.source)
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Subscribe to updates about changes in this graph.
|
||||
@ -191,17 +273,17 @@ impl Handle {
|
||||
}
|
||||
|
||||
/// Retrieves metadata for the given node.
|
||||
pub fn node_metadata(&self, id:ast::ID) -> FallibleResult<NodeMetadata> {
|
||||
pub fn node_metadata(&self, id:ast::Id) -> FallibleResult<NodeMetadata> {
|
||||
self.module().node_metadata(id)
|
||||
}
|
||||
|
||||
/// Modify metadata of given node.
|
||||
/// If ID doesn't have metadata, empty (default) metadata is inserted.
|
||||
pub fn with_node_metadata(&self, id:ast::ID, fun:impl FnOnce(&mut NodeMetadata)) {
|
||||
pub fn with_node_metadata(&self, id:ast::Id, fun:impl FnOnce(&mut NodeMetadata)) {
|
||||
let module = self.module();
|
||||
let mut data = module.pop_node_metadata(id).unwrap_or_default();
|
||||
fun(&mut data);
|
||||
module.set_node_metadata(id, data);
|
||||
module.set_node_metadata(id,data);
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,6 +305,7 @@ mod tests {
|
||||
use parser::Parser;
|
||||
use utils::test::ExpectTuple;
|
||||
use wasm_bindgen_test::wasm_bindgen_test;
|
||||
use ast::test_utils::expect_shape;
|
||||
|
||||
struct GraphControllerFixture(TestWithLocalPoolExecutor);
|
||||
impl GraphControllerFixture {
|
||||
@ -231,7 +314,7 @@ mod tests {
|
||||
Self(nested)
|
||||
}
|
||||
|
||||
pub fn run_graph_for_program<Test,Fut>(&mut self, code:impl Str, function_name:impl Str, test:Test)
|
||||
pub fn run_graph_for_main<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());
|
||||
@ -245,13 +328,26 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run_graph_for<Test,Fut>(&mut self, code:impl Str, graph_id:Id, 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 = 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)
|
||||
self.run_graph_for_main(code, name, test)
|
||||
}
|
||||
}
|
||||
|
||||
@ -281,7 +377,7 @@ mod tests {
|
||||
#[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 {
|
||||
test.run_graph_for_main("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();
|
||||
|
||||
@ -312,11 +408,126 @@ mod tests {
|
||||
main =
|
||||
foo = 2
|
||||
print foo";
|
||||
test.run_graph_for_program(program, "main", |_,graph| async move {
|
||||
test.run_graph_for_main(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");
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn graph_controller_parse_expression() {
|
||||
let mut test = GraphControllerFixture::set_up();
|
||||
let program = r"main = 0";
|
||||
test.run_graph_for_main(program, "main", |_, graph| async move {
|
||||
let foo = graph.parse_node_expression("foo").unwrap();
|
||||
assert_eq!(expect_shape::<ast::Var>(&foo), &ast::Var {name:"foo".into()});
|
||||
|
||||
assert!(graph.parse_node_expression("Vec").is_ok());
|
||||
assert!(graph.parse_node_expression("5").is_ok());
|
||||
assert!(graph.parse_node_expression("5+5").is_ok());
|
||||
assert!(graph.parse_node_expression("a+5").is_ok());
|
||||
assert!(graph.parse_node_expression("a=5").is_err());
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn graph_controller_nested_definition() {
|
||||
let mut test = GraphControllerFixture::set_up();
|
||||
const PROGRAM:&str = r"main =
|
||||
foo a =
|
||||
bar b = 5
|
||||
print foo";
|
||||
let definition = definition::Id::new_plain_names(vec!["main","foo"]);
|
||||
test.run_graph_for(PROGRAM, definition, |module, graph| async move {
|
||||
let expression = "new_node";
|
||||
graph.add_node(NewNodeInfo::new_pushed_back(expression)).unwrap();
|
||||
let expected_program = r"main =
|
||||
foo a =
|
||||
bar b = 5
|
||||
new_node
|
||||
print foo";
|
||||
module.expect_code(expected_program);
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn graph_controller_doubly_nested_definition() {
|
||||
// Tests editing nested definition that requires transforming inline expression into
|
||||
// into a new block.
|
||||
let mut test = GraphControllerFixture::set_up();
|
||||
// Not using multi-line raw string literals, as we don't want IntelliJ to automatically
|
||||
// strip the trailing whitespace in the lines.
|
||||
const PROGRAM:&str = "main =\n foo a =\n bar b = 5\n print foo";
|
||||
let definition = definition::Id::new_plain_names(vec!["main","foo","bar"]);
|
||||
test.run_graph_for(PROGRAM, definition, |module, graph| async move {
|
||||
let expression = "new_node";
|
||||
graph.add_node(NewNodeInfo::new_pushed_back(expression)).unwrap();
|
||||
let expected_program = "main =\n foo a =\n bar b = \
|
||||
\n 5\n new_node\n print foo";
|
||||
module.expect_code(expected_program);
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn graph_controller_node_operations_node() {
|
||||
let mut test = GraphControllerFixture::set_up();
|
||||
const PROGRAM:&str = r"
|
||||
main =
|
||||
foo = 2
|
||||
print foo";
|
||||
test.run_graph_for_main(PROGRAM, "main", |module, graph| async move {
|
||||
// === Initial nodes ===
|
||||
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");
|
||||
|
||||
|
||||
// === Add node ===
|
||||
let id = ast::Id::new_v4();
|
||||
let position = Some(controller::module::Position::new(10.0,20.0));
|
||||
let metadata = NodeMetadata {position};
|
||||
let info = NewNodeInfo {
|
||||
expression : "a+b".into(),
|
||||
metadata : Some(metadata),
|
||||
id : Some(id),
|
||||
location_hint : LocationHint::End,
|
||||
};
|
||||
graph.add_node(info).unwrap();
|
||||
let expected_program = r"
|
||||
main =
|
||||
foo = 2
|
||||
print foo
|
||||
a+b";
|
||||
assert_eq!(module.code(), expected_program);
|
||||
let nodes = graph.nodes().unwrap();
|
||||
let (_,_,node3) = nodes.expect_tuple();
|
||||
assert_eq!(node3.info.id(),id);
|
||||
assert_eq!(node3.info.expression().repr(), "a+b");
|
||||
let pos = node3.metadata.unwrap().position;
|
||||
assert_eq!(pos, position);
|
||||
assert!(graph.node_metadata(id).is_ok());
|
||||
|
||||
|
||||
// === Edit node ===
|
||||
graph.set_expression(id, "bar baz").unwrap();
|
||||
let (_,_,node3) = graph.nodes().unwrap().expect_tuple();
|
||||
assert_eq!(node3.info.id(),id);
|
||||
assert_eq!(node3.info.expression().repr(), "bar baz");
|
||||
assert_eq!(node3.metadata.unwrap().position, position);
|
||||
|
||||
|
||||
// === Remove node ===
|
||||
graph.remove_node(node3.info.id()).unwrap();
|
||||
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");
|
||||
assert!(graph.node_metadata(id).is_err());
|
||||
|
||||
assert_eq!(module.code(), PROGRAM);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -33,14 +33,14 @@ use shapely::shared;
|
||||
|
||||
|
||||
|
||||
/// ============
|
||||
/// == Errors ==
|
||||
/// ============
|
||||
// ============
|
||||
// == Errors ==
|
||||
// ============
|
||||
|
||||
/// Failure for missing node metadata.
|
||||
#[derive(Debug,Clone,Copy,Fail)]
|
||||
#[fail(display="Node with ID {} was not found in metadata.", _0)]
|
||||
pub struct NodeMetadataNotFound(pub ast::ID);
|
||||
pub struct NodeMetadataNotFound(pub ast::Id);
|
||||
|
||||
|
||||
|
||||
@ -66,7 +66,7 @@ impl parser::api::Metadata for Metadata {}
|
||||
#[derive(Debug,Clone,Default,Deserialize,Serialize)]
|
||||
pub struct IdeMetadata {
|
||||
/// Metadata that belongs to nodes.
|
||||
node : HashMap<ast::ID,NodeMetadata>
|
||||
node : HashMap<ast::Id,NodeMetadata>
|
||||
}
|
||||
|
||||
/// Metadata of specific node.
|
||||
@ -83,6 +83,13 @@ pub struct Position {
|
||||
pub vector:Vector2<f32>
|
||||
}
|
||||
|
||||
impl Position {
|
||||
/// Creates a new position with given coordinates.
|
||||
pub fn new(x:f32, y:f32) -> Position {
|
||||
let vector = Vector2::new(x,y);
|
||||
Position {vector}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -161,12 +168,17 @@ shared! { Handle
|
||||
code.replace_range(replaced_indices,&change.inserted);
|
||||
apply_code_change_to_id_map(&mut id_map,&replaced_span,&change.inserted);
|
||||
let ast = self.parser.parse(code, id_map)?;
|
||||
self.update_ast(ast);
|
||||
self.update_ast(ast.try_into()?);
|
||||
self.logger.trace(|| format!("Applied change; Ast is now {:?}", self.module.ast));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Obtain parser handle.
|
||||
pub fn parser(&self) -> Parser {
|
||||
self.parser.clone()
|
||||
}
|
||||
|
||||
/// Read module code.
|
||||
pub fn code(&self) -> String {
|
||||
self.module.ast.repr()
|
||||
@ -175,7 +187,7 @@ shared! { Handle
|
||||
/// 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)
|
||||
double_representation::definition::traverse_for_definition(&module,id)
|
||||
}
|
||||
|
||||
/// Check if current module state is synchronized with given code. If it's not, log error,
|
||||
@ -201,21 +213,37 @@ shared! { Handle
|
||||
}
|
||||
|
||||
/// Returns metadata for given node, if present.
|
||||
pub fn node_metadata(&mut self, id:ast::ID) -> FallibleResult<NodeMetadata> {
|
||||
pub fn node_metadata(&mut self, id:ast::Id) -> FallibleResult<NodeMetadata> {
|
||||
let data = self.module.metadata.ide.node.get(&id).cloned();
|
||||
data.ok_or_else(|| NodeMetadataNotFound(id).into())
|
||||
}
|
||||
|
||||
/// Sets metadata for given node.
|
||||
pub fn set_node_metadata(&mut self, id:ast::ID, data:NodeMetadata) {
|
||||
pub fn set_node_metadata(&mut self, id:ast::Id, data:NodeMetadata) {
|
||||
self.module.metadata.ide.node.insert(id,data);
|
||||
}
|
||||
|
||||
/// Removes metadata of given node and returns them.
|
||||
pub fn pop_node_metadata(&mut self, id:ast::ID) -> FallibleResult<NodeMetadata> {
|
||||
pub fn pop_node_metadata(&mut self, id:ast::Id) -> FallibleResult<NodeMetadata> {
|
||||
let data = self.module.metadata.ide.node.remove(&id);
|
||||
data.ok_or_else(|| NodeMetadataNotFound(id).into())
|
||||
}
|
||||
|
||||
/// Update current ast in module controller and emit notification about overall
|
||||
/// invalidation.
|
||||
pub fn update_ast(&mut self, ast:known::Module) {
|
||||
self.module.ast = ast.into();
|
||||
let text_change = notification::Text::Invalidate;
|
||||
let graph_change = notification::Graphs::Invalidate;
|
||||
let code_notify = self.text_notifications.publish(text_change);
|
||||
let graph_notify = self.graph_notifications.publish(graph_change);
|
||||
spawn(async move { futures::join!(code_notify,graph_notify); });
|
||||
}
|
||||
|
||||
/// Returns the current module's AST.
|
||||
pub fn ast(&mut self) -> FallibleResult<known::Module> {
|
||||
Ok(known::Module::try_from(&self.module.ast)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,8 +282,9 @@ impl Handle {
|
||||
let SourceFile{ast,metadata} = parser.parse_with_metadata(content)?;
|
||||
logger.info(|| "Code parsed");
|
||||
logger.trace(|| format!("The parsed ast is {:?}", ast));
|
||||
let module_ast = known::Module::try_new(ast)?;
|
||||
self.with_borrowed(|data| data.module.metadata = metadata);
|
||||
self.with_borrowed(|data| data.update_ast(ast));
|
||||
self.update_ast(module_ast);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -294,21 +323,14 @@ impl Handle {
|
||||
text_notifications,graph_notifications};
|
||||
Ok(Handle::new_from_data(data))
|
||||
}
|
||||
}
|
||||
|
||||
impl Controller {
|
||||
/// Update current ast in module controller and emit notification about overall invalidation.
|
||||
fn update_ast(&mut self,ast:Ast) {
|
||||
self.module.ast = ast;
|
||||
let text_change = notification::Text::Invalidate;
|
||||
let graph_change = notification::Graphs::Invalidate;
|
||||
let code_notify = self.text_notifications.publish(text_change);
|
||||
let graph_notify = self.graph_notifications.publish(graph_change);
|
||||
spawn(async move { futures::join!(code_notify,graph_notify); });
|
||||
#[cfg(test)]
|
||||
pub fn expect_code(&self, expected_code:impl Str) {
|
||||
let code = self.code();
|
||||
assert_eq!(code,expected_code.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =============
|
||||
// === Tests ===
|
||||
// =============
|
||||
|
@ -5,3 +5,17 @@ pub mod definition;
|
||||
pub mod graph;
|
||||
pub mod node;
|
||||
pub mod text;
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Consts ===
|
||||
// ==============
|
||||
|
||||
/// Indentation value from language specification:
|
||||
///
|
||||
/// Indentation: Indentation is four spaces, and all tabs are converted to 4 spaces. This is not
|
||||
/// configurable on purpose.
|
||||
///
|
||||
/// Link: https://github.com/luna/enso/blob/master/doc/design/syntax/syntax.md#encoding
|
||||
pub const INDENT : usize = 4;
|
||||
|
@ -2,13 +2,114 @@
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use ast::Ast;
|
||||
use ast::crumbs::ChildAst;
|
||||
use ast::crumbs::Crumbable;
|
||||
use ast::HasRepr;
|
||||
use ast::Shape;
|
||||
use ast::known;
|
||||
use ast::prefix;
|
||||
use ast::opr;
|
||||
use shapely::EmptyIterator;
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === Definition 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}
|
||||
}
|
||||
|
||||
/// Creates a new identifier with a single plain name.
|
||||
pub fn new_plain_name(name:impl Str) -> Id {
|
||||
Self::new_plain_names(std::iter::once(name.into()))
|
||||
}
|
||||
|
||||
/// Creates a new identifier from a sequence of plain definition names.
|
||||
pub fn new_plain_names<S>(names:impl IntoIterator<Item = S>) -> Id
|
||||
where S:ToString {
|
||||
let crumbs = names.into_iter().map(|name| {
|
||||
DefinitionName::new_plain(name.to_string())
|
||||
}).collect_vec();
|
||||
Id {crumbs}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Id {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut iter = self.crumbs.iter();
|
||||
if let Some(crumb) = iter.next() {
|
||||
write!(f, "{}", crumb)?
|
||||
}
|
||||
for crumb in iter {
|
||||
write!(f, "⮚{}", crumb)?
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===============================
|
||||
// === Finding Graph In Module ===
|
||||
// ===============================
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Fail,Clone,Debug)]
|
||||
#[fail(display="Cannot find definition child by id {:?}.",_0)]
|
||||
pub struct CannotFindChild(Crumb);
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Copy,Fail,Clone,Debug)]
|
||||
#[fail(display="Encountered an empty definition ID. They must contain at least one crumb.")]
|
||||
pub struct EmptyDefinitionId;
|
||||
|
||||
/// Looks up graph in the module.
|
||||
pub fn traverse_for_definition
|
||||
(ast:&ast::known::Module, id:&Id) -> FallibleResult<DefinitionInfo> {
|
||||
Ok(locate(ast, id)?.item)
|
||||
}
|
||||
|
||||
/// Traverses the given sequence of crumbs, looking up the definition by given ID.
|
||||
pub fn locate(ast:&ast::known::Module, id:&Id) -> FallibleResult<ChildDefinition> {
|
||||
let mut crumbs_iter = id.crumbs.iter();
|
||||
// Not exactly regular - first crumb is a little special, because module is not a definition
|
||||
// nor a children.
|
||||
let first_crumb = crumbs_iter.next().ok_or(EmptyDefinitionId)?;
|
||||
let mut child = ast.def_iter().find_by_name(&first_crumb)?;
|
||||
for crumb in crumbs_iter {
|
||||
child = resolve_single_name(child,crumb)?;
|
||||
}
|
||||
Ok(child)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Error ===
|
||||
// =============
|
||||
|
||||
#[derive(Fail,Debug)]
|
||||
#[fail(display="Cannot set Block lines because no line with Some(Ast) was found. Block must have \
|
||||
at least one non-empty line.")]
|
||||
struct MissingLineWithAst;
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
@ -103,6 +204,7 @@ impl Display for DefinitionName {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ======================
|
||||
// === DefinitionInfo ===
|
||||
// ======================
|
||||
@ -112,11 +214,13 @@ impl Display for DefinitionName {
|
||||
pub struct DefinitionInfo {
|
||||
/// The whole definition. It is an Infix shape with `=` operator. Its left-hand side is
|
||||
/// an App.
|
||||
pub ast: known::Infix,
|
||||
pub ast:known::Infix,
|
||||
/// Name of this definition. Includes typename, if this is an extension method.
|
||||
pub name: DefinitionName,
|
||||
pub name:DefinitionName,
|
||||
/// Arguments for this definition. Does not include any implicit ones (e.g. no `this`).
|
||||
pub args: Vec<Ast>,
|
||||
pub args:Vec<Ast>,
|
||||
/// The absolute indentation of the code block that introduced this definition.
|
||||
pub context_indent:usize
|
||||
}
|
||||
|
||||
impl DefinitionInfo {
|
||||
@ -125,18 +229,67 @@ impl DefinitionInfo {
|
||||
self.ast.rarg.clone()
|
||||
}
|
||||
|
||||
/// 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)
|
||||
/// Gets the definition block lines. If `body` is a `Block`, it returns its `BlockLine`s,
|
||||
/// concatenating `empty_lines`, `first_line` and `lines`, in this exact order. If `body` is
|
||||
/// `Infix`, it returns a single `BlockLine`.
|
||||
pub fn block_lines(&self) -> FallibleResult<Vec<ast::BlockLine<Option<Ast>>>> {
|
||||
if let Ok(block) = known::Block::try_from(self.body()) {
|
||||
Ok(block.all_lines())
|
||||
} else {
|
||||
let elem = Some(self.body());
|
||||
let off = 0;
|
||||
Ok(vec![ast::BlockLine{elem,off}])
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the definition block lines. `lines` must contain at least one non-empty line to
|
||||
/// succeed.
|
||||
pub fn set_block_lines
|
||||
(&mut self, mut lines:Vec<ast::BlockLine<Option<Ast>>>) -> FallibleResult<()> {
|
||||
// FIXME [mwu]
|
||||
// This doesn't deal correctly with offsets, but I have no idea how it should behave,
|
||||
// as the current parser's behavior and AST is inconsistent.
|
||||
// Basically `empty_lines` currently use absolute offsets, while `BlockLines` use relative
|
||||
// offsets. This is not desirable, as e.g. an empty line in the middle of block is not
|
||||
// possible to express with the current AST (it won't round-trip).
|
||||
|
||||
let indent = self.context_indent + double_representation::INDENT;
|
||||
let mut empty_lines = Vec::new();
|
||||
let mut line = lines.pop_front().ok_or(MissingLineWithAst)?;
|
||||
while let None = line.elem {
|
||||
empty_lines.push(line.off + indent);
|
||||
line = lines.pop_front().ok_or(MissingLineWithAst)?;
|
||||
}
|
||||
let elem = line.elem.ok_or(MissingLineWithAst)?;
|
||||
let off = line.off;
|
||||
let first_line = ast::BlockLine {elem,off};
|
||||
let is_orphan = false;
|
||||
let ty = ast::BlockType::Discontinuous {};
|
||||
let block = ast::Block {empty_lines,first_line,lines,indent,is_orphan,ty};
|
||||
let body_ast = Ast::new(block,None);
|
||||
self.set_body_ast(body_ast);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the definition body to expression with given Ast.
|
||||
pub fn set_body_ast(&mut self, expression:Ast) {
|
||||
self.ast.update_shape(|infix| {
|
||||
// Keep at least one space after `=` for inline expressions, so it
|
||||
// doesn't look ugly when converting from previous block definition body.
|
||||
match expression.shape() {
|
||||
ast::Shape::Block(_) => {}
|
||||
_ => infix.roff = std::cmp::max(1,infix.roff),
|
||||
}
|
||||
infix.rarg = expression;
|
||||
})
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
pub fn from_line_ast
|
||||
(ast:&Ast, kind:ScopeKind, context_indent:usize) -> 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.
|
||||
@ -144,7 +297,7 @@ impl DefinitionInfo {
|
||||
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};
|
||||
let ret = DefinitionInfo {ast:infix,name,args,context_indent};
|
||||
|
||||
// Note [Scope Differences]
|
||||
if kind == ScopeKind::NonRoot {
|
||||
@ -172,6 +325,58 @@ impl DefinitionInfo {
|
||||
// 2. Expression like "foo = 5". In module, this is treated as method definition (with implicit
|
||||
// this parameter). In definition, this is just a node (evaluated expression).
|
||||
|
||||
/// Definition stored under some known crumbs path.
|
||||
pub type ChildDefinition = ast::crumbs::Located<DefinitionInfo>;
|
||||
|
||||
/// Tries to add a new crumb to current path to obtain a deeper child.
|
||||
/// Its crumbs will accumulate both current crumbs and the passed one.
|
||||
pub fn resolve_single_name(def:ChildDefinition, id:&Crumb) -> FallibleResult<ChildDefinition> {
|
||||
let child = def.item.def_iter().find_by_name(id)?;
|
||||
Ok(def.push_descendant(child))
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==========================
|
||||
// === DefinitionIterator ===
|
||||
// ==========================
|
||||
|
||||
/// Iterator that iterates over child definitions.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct DefinitionIterator<'a> {
|
||||
/// Iterator going over ASTs of potential child definitions.
|
||||
pub iterator : Box<dyn Iterator<Item = ChildAst<'a>>+'a>,
|
||||
/// What kind of scope are we getting our ASTs from.
|
||||
pub scope_kind : ScopeKind,
|
||||
/// Absolute indentation of the child ASTs we iterate over.
|
||||
pub indent : usize,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for DefinitionIterator<'a> {
|
||||
type Item = ChildDefinition;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let scope_kind = self.scope_kind;
|
||||
let indent = self.indent;
|
||||
self.iterator.find_map(|ChildAst {item,crumbs}| {
|
||||
let definition_opt = DefinitionInfo::from_line_ast(item,scope_kind,indent);
|
||||
definition_opt.map(|def| ChildDefinition::new(crumbs,def))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DefinitionIterator<'a> {
|
||||
/// Yields vector of all child definition infos, discarding the crumbs.
|
||||
pub fn infos_vec(self) -> Vec<DefinitionInfo> {
|
||||
self.map(|child_def| child_def.item).collect_vec()
|
||||
}
|
||||
|
||||
/// Looks up direct child definition by given name.
|
||||
pub fn find_by_name(mut self, name:&DefinitionName) -> Result<ChildDefinition,CannotFindChild> {
|
||||
let err = || CannotFindChild(name.clone());
|
||||
self.find(|child_def| &child_def.item.name == name).ok_or_else(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==========================
|
||||
@ -180,48 +385,82 @@ impl DefinitionInfo {
|
||||
|
||||
/// An entity that contains lines that we want to interpret as definitions.
|
||||
pub trait DefinitionProvider {
|
||||
/// Absolute indentation level of the scope of this provider's body.
|
||||
/// (i.e. the indents of the child definitions)
|
||||
fn indent(&self) -> usize;
|
||||
|
||||
/// What kind of scope this is.
|
||||
fn scope_kind() -> ScopeKind;
|
||||
fn scope_kind(&self) -> ScopeKind;
|
||||
|
||||
/// Iterates over non-empty lines' ASTs.
|
||||
fn line_asts<'a>(&'a self) -> Box<dyn Iterator<Item=&'a Ast> + 'a>;
|
||||
/// Iterator going over all line-like Ast's that can hold a child definition.
|
||||
fn enumerate_asts<'a>(&'a self) -> Box<dyn Iterator<Item = ChildAst<'a>>+'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 a scope iterator allowing browsing definition provided under this provider.
|
||||
fn def_iter(&self) -> DefinitionIterator {
|
||||
let iterator = self.enumerate_asts();
|
||||
let scope_kind = self.scope_kind();
|
||||
let indent = self.indent();
|
||||
DefinitionIterator {iterator,scope_kind,indent}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)
|
||||
})}
|
||||
/// Enumerates all AST being a direct children of the given AST node.
|
||||
pub fn ast_direct_children<'a>
|
||||
(ast:&'a impl Crumbable) -> Box<dyn Iterator<Item = ChildAst<'a>>+'a> {
|
||||
let iter = ast.enumerate().map(|(crumb,ast)| {
|
||||
ChildAst::new_direct_child(crumb,ast)
|
||||
});
|
||||
Box::new(iter)
|
||||
}
|
||||
|
||||
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())
|
||||
fn indent(&self) -> usize { 0 }
|
||||
|
||||
fn scope_kind(&self) -> ScopeKind { ScopeKind::Root }
|
||||
|
||||
fn enumerate_asts<'a>(&'a self) -> Box<dyn Iterator<Item = ChildAst<'a>>+'a> {
|
||||
ast_direct_children(self.ast())
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
fn indent(&self) -> usize { self.indent }
|
||||
|
||||
fn scope_kind(&self) -> ScopeKind { ScopeKind::NonRoot }
|
||||
|
||||
fn enumerate_asts<'a>(&'a self) -> Box<dyn Iterator<Item = ChildAst<'a>>+'a> {
|
||||
ast_direct_children(self.ast())
|
||||
}
|
||||
}
|
||||
|
||||
impl DefinitionProvider for DefinitionInfo {
|
||||
fn scope_kind() -> ScopeKind { ScopeKind::NonRoot }
|
||||
fn line_asts<'a>(&'a self) -> Box<dyn Iterator<Item=&'a Ast> + 'a> {
|
||||
fn indent(&self) -> usize {
|
||||
match self.ast.rarg.shape() {
|
||||
ast::Shape::Block(_) => self.ast.rarg.iter(),
|
||||
_ => Box::new(EmptyIterator::new())
|
||||
ast::Shape::Block(block) => block.indent,
|
||||
// If definition has no block of its own, it does not introduce any children and
|
||||
// returned value here is not used anywhere currently. Might matter in the future,
|
||||
// when we deal with lambdas. Anyway, whatever block we might introduce, it should
|
||||
// be more indented than our current context.
|
||||
_ => self.context_indent + double_representation::INDENT,
|
||||
}
|
||||
}
|
||||
|
||||
fn scope_kind(&self) -> ScopeKind { ScopeKind::NonRoot }
|
||||
|
||||
fn enumerate_asts<'a>(&'a self) -> Box<dyn Iterator<Item = ChildAst<'a>>+'a> {
|
||||
use ast::crumbs::Crumb;
|
||||
use ast::crumbs::InfixCrumb;
|
||||
match self.ast.rarg.shape() {
|
||||
ast::Shape::Block(_) => {
|
||||
let parent_crumb = Crumb::Infix(InfixCrumb::RightOperand);
|
||||
let rarg = &self.ast.rarg;
|
||||
let iter = rarg.enumerate().map(move |(crumb,ast)| {
|
||||
let crumbs = vec![parent_crumb,crumb];
|
||||
ChildAst::new(crumbs,ast)
|
||||
});
|
||||
Box::new(iter)
|
||||
}
|
||||
_ => Box::new(std::iter::empty())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -235,6 +474,7 @@ impl DefinitionProvider for DefinitionInfo {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use parser::api::IsParser;
|
||||
use utils::test::ExpectTuple;
|
||||
use wasm_bindgen_test::wasm_bindgen_test;
|
||||
@ -279,15 +519,15 @@ mod tests {
|
||||
// In definition there are no extension methods nor arg-less definitions.
|
||||
let expected_def_names_in_def = vec!["add", "*"];
|
||||
|
||||
// === Program with defnitions in root ===
|
||||
// === Program with definitions in root ===
|
||||
let program = definition_lines.join("\n");
|
||||
let module = parser.parse_module(program.into(), default()).unwrap();
|
||||
let definitions = module.list_definitions();
|
||||
let module = parser.parse_module(program, default()).unwrap();
|
||||
let definitions = module.def_iter().infos_vec();
|
||||
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 = module.find_definition(&add_name).expect("failed to find `add` function");
|
||||
let add = module.def_iter().find_by_name(&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");
|
||||
|
||||
@ -295,11 +535,65 @@ mod tests {
|
||||
let indented_lines = definition_lines.iter().map(indented).collect_vec();
|
||||
let program = format!("some_func arg1 arg2 =\n{}", indented_lines.join("\n"));
|
||||
let module = parser.parse_module(program,default()).unwrap();
|
||||
let root_defs = module.list_definitions();
|
||||
let root_defs = module.def_iter().infos_vec();
|
||||
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 = body_block.list_definitions();
|
||||
let nested_defs = body_block.def_iter().infos_vec();
|
||||
assert_eq_strings(to_names(&nested_defs),expected_def_names_in_def);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn finding_root_definition() {
|
||||
let program_to_expected_main_pos = vec![
|
||||
("main = bar", 0),
|
||||
("\nmain = bar", 1),
|
||||
("\n\nmain = bar", 2),
|
||||
("foo = bar\nmain = bar", 1),
|
||||
("foo = bar\n\nmain = bar", 2),
|
||||
];
|
||||
|
||||
let mut parser = parser::Parser::new_or_panic();
|
||||
let main_id = Id::new_plain_name("main");
|
||||
for (program,expected_line_index) in program_to_expected_main_pos {
|
||||
let module = parser.parse_module(program,default()).unwrap();
|
||||
let location = locate(&module, &main_id).unwrap();
|
||||
let (crumb,) = location.crumbs.expect_tuple();
|
||||
match crumb {
|
||||
ast::crumbs::Crumb::Module(m) => assert_eq!(m.line_index, expected_line_index),
|
||||
_ => panic!("Expected module crumb, got: {:?}.", crumb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn getting_nested_definition() {
|
||||
let program = r"
|
||||
main =
|
||||
foo = 2
|
||||
add a b = a + b
|
||||
baz arg =
|
||||
subbaz arg = 4
|
||||
baz2 arg =
|
||||
subbaz2 = 4
|
||||
|
||||
add foo bar";
|
||||
|
||||
let module = parser::Parser::new_or_panic().parse_module(program,default()).unwrap();
|
||||
let check_def = |id, expected_body| {
|
||||
let definition = traverse_for_definition(&module,&id).unwrap();
|
||||
assert_eq!(definition.body().repr(), expected_body);
|
||||
};
|
||||
let check_not_found = |id| {
|
||||
assert!(traverse_for_definition(&module,&id).is_err())
|
||||
};
|
||||
|
||||
check_def(Id::new_plain_names(&["main","add"]), "a + b");
|
||||
check_def(Id::new_plain_names(&["main","baz"]), "\n subbaz arg = 4");
|
||||
check_def(Id::new_plain_names(&["main","baz","subbaz"]), "4");
|
||||
|
||||
// Node are not definitions
|
||||
check_not_found(Id::new_plain_names(&["main", "foo"]));
|
||||
check_not_found(Id::new_plain_names(&["main","baz2","subbaz2"]));
|
||||
}
|
||||
}
|
||||
|
@ -4,64 +4,43 @@ use crate::prelude::*;
|
||||
|
||||
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::NodeInfo;
|
||||
|
||||
use ast::Ast;
|
||||
use ast::BlockLine;
|
||||
use ast::known;
|
||||
use utils::fail::FallibleResult;
|
||||
|
||||
/// Graph uses the same `Id` as the definition which introduces the graph.
|
||||
pub type Id = double_representation::definition::Id;
|
||||
|
||||
|
||||
|
||||
// ================
|
||||
// === Graph Id ===
|
||||
// ================
|
||||
// =============
|
||||
// === Error ===
|
||||
// =============
|
||||
|
||||
/// 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}
|
||||
}
|
||||
}
|
||||
#[derive(Fail,Debug)]
|
||||
#[fail(display="ID was not found.")]
|
||||
struct IdNotFound {id:ast::Id}
|
||||
|
||||
|
||||
// ===============================
|
||||
// === Finding Graph In Module ===
|
||||
// ===============================
|
||||
|
||||
#[derive(Fail,Clone,Debug)]
|
||||
#[fail(display="Definition ID was empty")]
|
||||
struct CannotFindDefinition(Id);
|
||||
// ====================
|
||||
// === LocationHint ===
|
||||
// ====================
|
||||
|
||||
#[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)
|
||||
/// 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,
|
||||
}
|
||||
|
||||
|
||||
@ -73,21 +52,18 @@ pub fn traverse_for_definition
|
||||
/// Description of the graph, based on information available in AST.
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct GraphInfo {
|
||||
source : DefinitionInfo,
|
||||
/// Describes all known nodes in this graph (does not include special pseudo-nodes like graph
|
||||
/// inputs and outputs).
|
||||
pub nodes : Vec<node::NodeInfo>,
|
||||
/// The definition providing this graph.
|
||||
pub source:DefinitionInfo,
|
||||
}
|
||||
|
||||
impl GraphInfo {
|
||||
/// Describe graph of the given definition.
|
||||
pub fn from_definition(source:DefinitionInfo) -> GraphInfo {
|
||||
let nodes = Self::from_function_binding(source.ast.clone());
|
||||
GraphInfo {source,nodes}
|
||||
GraphInfo {source}
|
||||
}
|
||||
|
||||
/// Lists nodes in the given binding's ast (infix expression).
|
||||
fn from_function_binding(ast:known::Infix) -> Vec<node::NodeInfo> {
|
||||
fn from_function_binding(ast:known::Infix) -> Vec<NodeInfo> {
|
||||
let body = ast.rarg.clone();
|
||||
if let Ok(body_block) = known::Block::try_new(body.clone()) {
|
||||
block_nodes(&body_block)
|
||||
@ -95,6 +71,90 @@ impl GraphInfo {
|
||||
expression_node(body)
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the AST of this graph definition.
|
||||
pub fn ast(&self) -> Ast {
|
||||
self.source.ast.clone().into()
|
||||
}
|
||||
|
||||
/// Gets all known nodes in this graph (does not include special pseudo-nodes like graph
|
||||
/// inputs and outputs).
|
||||
pub fn nodes(&self) -> Vec<NodeInfo> {
|
||||
Self::from_function_binding(self.source.ast.clone())
|
||||
}
|
||||
|
||||
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);
|
||||
id_matches.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Searches for `NodeInfo` with the associated `id` index in `lines`. Returns an error if
|
||||
/// the Id is not found.
|
||||
pub fn find_node_index_in_lines
|
||||
(lines:&[BlockLine<Option<Ast>>], id:ast::Id) -> FallibleResult<usize> {
|
||||
let position = lines.iter().position(|line| Self::is_node_by_id(&line,id));
|
||||
position.ok_or_else(|| IdNotFound{id}.into())
|
||||
}
|
||||
|
||||
/// Adds a new node to this graph.
|
||||
pub fn add_node
|
||||
(&mut self, line_ast:Ast, location_hint:LocationHint) -> FallibleResult<()> {
|
||||
let mut lines = self.source.block_lines()?;
|
||||
let index = match location_hint {
|
||||
LocationHint::Start => 0,
|
||||
LocationHint::End => lines.len(),
|
||||
LocationHint::After(id) => Self::find_node_index_in_lines(&lines, id)? + 1,
|
||||
LocationHint::Before(id) => Self::find_node_index_in_lines(&lines, id)?
|
||||
};
|
||||
let elem = Some(line_ast);
|
||||
let off = 0;
|
||||
lines.insert(index,BlockLine{elem,off});
|
||||
self.source.set_block_lines(lines)
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
Ast::cons("Nothing").with_new_id()
|
||||
}
|
||||
|
||||
/// Removes the node from graph.
|
||||
pub fn remove_node(&mut self, node_id:ast::Id) -> FallibleResult<()> {
|
||||
let mut lines = self.source.block_lines()?;
|
||||
lines.drain_filter(|line| {
|
||||
let node = line.elem.as_ref().and_then(NodeInfo::from_line_ast);
|
||||
let removed_node = node.filter(|node| node.id() == node_id);
|
||||
removed_node.is_some()
|
||||
});
|
||||
if lines.is_empty() {
|
||||
self.source.set_body_ast(Self::empty_graph_body())
|
||||
} else {
|
||||
self.source.set_block_lines(lines)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets expression of the given node.
|
||||
pub fn edit_node(&mut self, node_id:ast::Id, new_expression:Ast) -> FallibleResult<()> {
|
||||
let mut lines = self.source.block_lines()?;
|
||||
let node_entry = lines.iter().enumerate().find_map(|(index,line)| {
|
||||
let node = line.elem.as_ref().and_then(NodeInfo::from_line_ast);
|
||||
let filtered = node.filter(|node| node.id() == node_id);
|
||||
filtered.map(|node| (index,node))
|
||||
});
|
||||
if let Some((index,mut node)) = node_entry {
|
||||
node.set_expression(new_expression);
|
||||
lines[index].elem = Some(node.ast().clone_ref());
|
||||
}
|
||||
self.source.set_block_lines(lines)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn expect_code(&self, expected_code:impl Str) {
|
||||
let code = self.source.ast.repr();
|
||||
assert_eq!(code,expected_code.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -104,19 +164,21 @@ impl GraphInfo {
|
||||
// =====================
|
||||
|
||||
/// Collects information about nodes in given code `Block`.
|
||||
pub fn block_nodes(ast:&known::Block) -> Vec<node::NodeInfo> {
|
||||
pub fn block_nodes(ast:&known::Block) -> Vec<NodeInfo> {
|
||||
ast.iter().flat_map(|line_ast| {
|
||||
let kind = definition::ScopeKind::NonRoot;
|
||||
let indent = ast.indent;
|
||||
// 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),
|
||||
match definition::DefinitionInfo::from_line_ast(line_ast,kind,indent) {
|
||||
None => 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()
|
||||
pub fn expression_node(ast:Ast) -> Vec<NodeInfo> {
|
||||
NodeInfo::new_expression(ast).into_iter().collect()
|
||||
}
|
||||
|
||||
|
||||
@ -131,9 +193,12 @@ mod tests {
|
||||
|
||||
use crate::double_representation::definition::DefinitionName;
|
||||
use crate::double_representation::definition::DefinitionProvider;
|
||||
use crate::double_representation::definition::traverse_for_definition;
|
||||
|
||||
use ast::HasRepr;
|
||||
use ast::test_utils::expect_single_line;
|
||||
use parser::api::IsParser;
|
||||
use utils::test::ExpectTuple;
|
||||
use wasm_bindgen_test::wasm_bindgen_test;
|
||||
|
||||
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
||||
@ -142,8 +207,18 @@ mod tests {
|
||||
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();
|
||||
GraphInfo::from_definition(main)
|
||||
let main = module.def_iter().find_by_name(&name).unwrap();
|
||||
GraphInfo::from_definition(main.item)
|
||||
}
|
||||
|
||||
fn find_graph(parser:&mut impl IsParser, program:impl Str, name:impl Str) -> GraphInfo {
|
||||
let module = parser.parse_module(program.into(), default()).unwrap();
|
||||
let crumbs = name.into().split(".").map(|name| {
|
||||
DefinitionName::new_plain(name)
|
||||
}).collect();
|
||||
let id = Id{crumbs};
|
||||
let definition = traverse_for_definition(&module,&id).unwrap();
|
||||
GraphInfo::from_definition(definition)
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
@ -158,13 +233,105 @@ mod tests {
|
||||
];
|
||||
for program in programs {
|
||||
let graph = main_graph(&mut parser, program);
|
||||
assert_eq!(graph.nodes.len(), 1);
|
||||
let node = &graph.nodes[0];
|
||||
let nodes = graph.nodes();
|
||||
assert_eq!(nodes.len(), 1);
|
||||
let node = &nodes[0];
|
||||
assert_eq!(node.expression().repr(), "2+2");
|
||||
let _ = node.id(); // just to make sure it is available
|
||||
}
|
||||
}
|
||||
|
||||
fn create_node_ast(parser:&mut impl IsParser, expression:&str) -> (Ast,ast::Id) {
|
||||
let node_ast = parser.parse(expression.to_string(), default()).unwrap();
|
||||
let line_ast = expect_single_line(&node_ast).clone();
|
||||
let id = line_ast.id.expect("line_ast should have an ID");
|
||||
(line_ast,id)
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn add_node_to_graph_with_single_line() {
|
||||
let program = "main = print \"hello\"";
|
||||
let mut parser = parser::Parser::new_or_panic();
|
||||
let mut graph = main_graph(&mut parser, program);
|
||||
let nodes = graph.nodes();
|
||||
assert_eq!(nodes.len(), 1);
|
||||
assert_eq!(nodes[0].expression().repr(), "print \"hello\"");
|
||||
|
||||
let expr0 = "a + 2";
|
||||
let expr1 = "b + 3";
|
||||
let (line_ast0,id0) = create_node_ast(&mut parser, expr0);
|
||||
let (line_ast1,id1) = create_node_ast(&mut parser, expr1);
|
||||
|
||||
graph.add_node(line_ast0, LocationHint::Start).unwrap();
|
||||
assert_eq!(graph.nodes().len(), 2);
|
||||
graph.add_node(line_ast1, LocationHint::Before(graph.nodes()[0].id())).unwrap();
|
||||
|
||||
let nodes = graph.nodes();
|
||||
assert_eq!(nodes.len(), 3);
|
||||
assert_eq!(nodes[0].expression().repr(), expr1);
|
||||
assert_eq!(nodes[0].id(), id1);
|
||||
assert_eq!(nodes[1].expression().repr(), expr0);
|
||||
assert_eq!(nodes[1].id(), id0);
|
||||
assert_eq!(nodes[2].expression().repr(), "print \"hello\"");
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn add_node_to_graph_with_multiple_lines() {
|
||||
// TODO [dg] Also add test for binding node when it's possible to update its id.
|
||||
let program = r#"main =
|
||||
foo = node
|
||||
foo a = not_node
|
||||
print "hello""#;
|
||||
let mut parser = parser::Parser::new_or_panic();
|
||||
let mut graph = main_graph(&mut parser, program);
|
||||
|
||||
let (line_ast0,id0) = create_node_ast(&mut parser, "4 + 4");
|
||||
let (line_ast1,id1) = create_node_ast(&mut parser, "a + b");
|
||||
let (line_ast2,id2) = create_node_ast(&mut parser, "x * x");
|
||||
let (line_ast3,id3) = create_node_ast(&mut parser, "x / x");
|
||||
let (line_ast4,id4) = create_node_ast(&mut parser, "2 - 2");
|
||||
|
||||
graph.add_node(line_ast0, LocationHint::Start).unwrap();
|
||||
graph.add_node(line_ast1, LocationHint::Before(graph.nodes()[0].id())).unwrap();
|
||||
graph.add_node(line_ast2, LocationHint::After(graph.nodes()[1].id())).unwrap();
|
||||
graph.add_node(line_ast3, LocationHint::End).unwrap();
|
||||
|
||||
let nodes = graph.nodes();
|
||||
assert_eq!(nodes.len(), 6);
|
||||
assert_eq!(nodes[0].expression().repr(), "a + b");
|
||||
assert_eq!(nodes[0].id(), id1);
|
||||
assert_eq!(nodes[1].expression().repr(), "4 + 4");
|
||||
assert_eq!(nodes[1].id(), id0);
|
||||
assert_eq!(nodes[2].expression().repr(), "x * x");
|
||||
assert_eq!(nodes[2].id(), id2);
|
||||
assert_eq!(nodes[3].expression().repr(), "node");
|
||||
assert_eq!(nodes[4].expression().repr(), "print \"hello\"");
|
||||
assert_eq!(nodes[5].expression().repr(), "x / x");
|
||||
assert_eq!(nodes[5].id(), id3);
|
||||
|
||||
let expected_code = r#"main =
|
||||
a + b
|
||||
4 + 4
|
||||
x * x
|
||||
foo = node
|
||||
foo a = not_node
|
||||
print "hello"
|
||||
x / x"#;
|
||||
graph.expect_code(expected_code);
|
||||
// TODO [mwu]
|
||||
// Test what happens with empty lines in the block.
|
||||
// Currently impossible because of the parser issue.
|
||||
|
||||
let mut graph = find_graph(&mut parser, program, "main.foo");
|
||||
|
||||
assert_eq!(graph.nodes().len(), 1);
|
||||
graph.add_node(line_ast4, LocationHint::Start).unwrap();
|
||||
assert_eq!(graph.nodes().len(), 2);
|
||||
assert_eq!(graph.nodes()[0].expression().repr(), "2 - 2");
|
||||
assert_eq!(graph.nodes()[0].id(), id4);
|
||||
assert_eq!(graph.nodes()[1].expression().repr(), "not_node");
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn multiple_node_graph() {
|
||||
let mut parser = parser::Parser::new_or_panic();
|
||||
@ -176,9 +343,82 @@ main =
|
||||
node
|
||||
";
|
||||
let graph = main_graph(&mut parser, program);
|
||||
assert_eq!(graph.nodes.len(), 2);
|
||||
for node in graph.nodes.iter() {
|
||||
let nodes = graph.nodes();
|
||||
assert_eq!(nodes.len(), 2);
|
||||
for node in nodes.iter() {
|
||||
assert_eq!(node.expression().repr(), "node");
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn removing_node_from_graph() {
|
||||
let mut parser = parser::Parser::new_or_panic();
|
||||
let program = r"
|
||||
main =
|
||||
foo = 2 + 2
|
||||
bar = 3 + 17";
|
||||
let mut graph = main_graph(&mut parser, program);
|
||||
let nodes = graph.nodes();
|
||||
assert_eq!(nodes.len(), 2);
|
||||
assert_eq!(nodes[0].expression().repr(), "2 + 2");
|
||||
assert_eq!(nodes[1].expression().repr(), "3 + 17");
|
||||
|
||||
graph.remove_node(nodes[0].id()).unwrap();
|
||||
|
||||
let nodes = graph.nodes();
|
||||
assert_eq!(nodes.len(), 1);
|
||||
assert_eq!(nodes[0].expression().repr(), "3 + 17");
|
||||
|
||||
let expected_code = "main =\n bar = 3 + 17";
|
||||
graph.expect_code(expected_code)
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn removing_last_node_from_graph() {
|
||||
let mut parser = parser::Parser::new_or_panic();
|
||||
let program = r"
|
||||
main =
|
||||
foo = 2 + 2";
|
||||
let mut graph = main_graph(&mut parser, program);
|
||||
println!("aa");
|
||||
let (node,) = graph.nodes().expect_tuple();
|
||||
assert_eq!(node.expression().repr(), "2 + 2");
|
||||
println!("vv");
|
||||
graph.remove_node(node.id()).unwrap();
|
||||
println!("zz");
|
||||
|
||||
let (node,) = graph.nodes().expect_tuple();
|
||||
assert_eq!(node.expression().repr(), "Nothing");
|
||||
graph.expect_code("main = Nothing");
|
||||
}
|
||||
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn editing_nodes_expression_in_graph() {
|
||||
let mut parser = parser::Parser::new_or_panic();
|
||||
let program = r"
|
||||
main =
|
||||
foo = 2 + 2
|
||||
bar = 3 + 17";
|
||||
let new_expression = parser.parse("print \"HELLO\"".to_string(), default()).unwrap();
|
||||
let new_expression = expect_single_line(&new_expression).clone();
|
||||
|
||||
let mut graph = main_graph(&mut parser, program);
|
||||
let nodes = graph.nodes();
|
||||
assert_eq!(nodes.len(), 2);
|
||||
assert_eq!(nodes[0].expression().repr(), "2 + 2");
|
||||
assert_eq!(nodes[1].expression().repr(), "3 + 17");
|
||||
|
||||
graph.edit_node(nodes[0].id(),new_expression).unwrap();
|
||||
|
||||
let nodes = graph.nodes();
|
||||
assert_eq!(nodes.len(), 2);
|
||||
assert_eq!(nodes[0].expression().repr(), "print \"HELLO\"");
|
||||
assert_eq!(nodes[1].expression().repr(), "3 + 17");
|
||||
|
||||
let expected_code = r#"main =
|
||||
foo = print "HELLO"
|
||||
bar = 3 + 17"#;
|
||||
graph.expect_code(expected_code)
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
//! Code for node discovery and other node-related tasks.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use ast::Ast;
|
||||
use ast::ID;
|
||||
use ast::Id;
|
||||
use ast::crumbs::Crumbable;
|
||||
use ast::known;
|
||||
|
||||
|
||||
@ -45,12 +48,27 @@ impl NodeInfo {
|
||||
}
|
||||
|
||||
/// Node's unique ID.
|
||||
pub fn id(&self) -> ID {
|
||||
pub fn id(&self) -> Id {
|
||||
// Panic must not happen, as the only available constructors checks that
|
||||
// there is an ID present.
|
||||
self.expression().id.expect("Node AST must bear an ID")
|
||||
}
|
||||
|
||||
/// Updates the node's AST so the node bears the given ID.
|
||||
pub fn set_id(&mut self, new_id:Id) {
|
||||
match self {
|
||||
NodeInfo::Binding{ref mut infix} => {
|
||||
let new_rarg = infix.rarg.with_id(new_id);
|
||||
let set = infix.set(&ast::crumbs::InfixCrumb::RightOperand.into(),new_rarg);
|
||||
*infix = set.expect("Internal error: setting infix operand should always \
|
||||
succeed.");
|
||||
}
|
||||
NodeInfo::Expression{ref mut ast} => {
|
||||
*ast = ast.with_id(new_id);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// AST of the node's expression.
|
||||
pub fn expression(&self) -> &Ast {
|
||||
match self {
|
||||
@ -58,6 +76,26 @@ impl NodeInfo {
|
||||
NodeInfo::Expression{ast} => &ast,
|
||||
}
|
||||
}
|
||||
|
||||
/// Mutable AST of the node's expression. Maintains ID.
|
||||
pub fn set_expression(&mut self, expression:Ast) {
|
||||
let id = self.id();
|
||||
match self {
|
||||
NodeInfo::Binding{ref mut infix} =>
|
||||
infix.update_shape(|infix| infix.rarg = expression),
|
||||
NodeInfo::Expression{ref mut ast} => *ast = expression,
|
||||
};
|
||||
// Id might have been overwritten by the AST we have set. Now we restore it.
|
||||
self.set_id(id);
|
||||
}
|
||||
|
||||
/// The whole AST of node.
|
||||
pub fn ast(&self) -> &Ast {
|
||||
match self {
|
||||
NodeInfo::Binding {infix} => infix.into(),
|
||||
NodeInfo::Expression{ast} => ast,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -70,29 +108,50 @@ impl NodeInfo {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use ast::HasRepr;
|
||||
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) {
|
||||
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().repr(),expression_text);
|
||||
assert_eq!(node_info.id(), id);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
#[test]
|
||||
fn expression_node_test() {
|
||||
// expression: `4`
|
||||
let id = ID::new_v4();
|
||||
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]
|
||||
#[test]
|
||||
fn set_expression_binding() {
|
||||
let ast = Ast::infix(Ast::var("foo"),"=",Ast::number(4).with_new_id());
|
||||
assert_eq!(ast.repr(), "foo = 4");
|
||||
|
||||
let mut node = NodeInfo::from_line_ast(&ast).expect("expected a node");
|
||||
let id = node.id();
|
||||
node.set_expression(Ast::var("bar"));
|
||||
assert_eq!(node.expression().repr(), "bar");
|
||||
assert_eq!(node.ast().repr(), "foo = bar");
|
||||
assert_eq!(node.id(), id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_expression_plain() {
|
||||
let ast = Ast::number(4).with_new_id();
|
||||
assert_eq!(ast.repr(), "4");
|
||||
|
||||
let mut node = NodeInfo::from_line_ast(&ast).expect("expected a node");
|
||||
let id = node.id();
|
||||
node.set_expression(Ast::var("bar"));
|
||||
assert_eq!(node.expression().repr(), "bar");
|
||||
assert_eq!(node.ast().repr(), "bar");
|
||||
assert_eq!(node.id(), id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn binding_node_test() {
|
||||
// expression: `foo = 4`
|
||||
let id = ID::new_v4();
|
||||
let id = Id::new_v4();
|
||||
let number = ast::Number { base:None, int: "4".into()};
|
||||
let larg = Ast::var("foo");
|
||||
let loff = 1;
|
||||
|
@ -4,6 +4,7 @@
|
||||
#![feature(bool_to_option)]
|
||||
#![feature(drain_filter)]
|
||||
#![feature(trait_alias)]
|
||||
#![recursion_limit="256"]
|
||||
#![warn(missing_docs)]
|
||||
#![warn(trivial_casts)]
|
||||
#![warn(trivial_numeric_casts)]
|
||||
@ -23,6 +24,7 @@ pub mod view;
|
||||
pub mod prelude {
|
||||
pub use ensogl::prelude::*;
|
||||
pub use enso_prelude::*;
|
||||
pub use ast::prelude::*;
|
||||
pub use wasm_bindgen::prelude::*;
|
||||
|
||||
pub use crate::constants;
|
||||
@ -37,6 +39,7 @@ pub mod prelude {
|
||||
pub use futures::task::LocalSpawnExt;
|
||||
|
||||
pub use utils::fail::FallibleResult;
|
||||
pub use utils::vec::VecExt;
|
||||
}
|
||||
|
||||
use crate::prelude::*;
|
||||
|
@ -16,3 +16,4 @@ pub mod channel;
|
||||
pub mod env;
|
||||
pub mod fail;
|
||||
pub mod test;
|
||||
pub mod vec;
|
||||
|
@ -91,3 +91,36 @@ for Collection {
|
||||
(v1,v2,v3)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Collection: IntoIterator>
|
||||
ExpectTuple<(Collection::Item,Collection::Item,Collection::Item,Collection::Item)>
|
||||
for Collection {
|
||||
fn expect_tuple
|
||||
(self) -> (Collection::Item,Collection::Item,Collection::Item,Collection::Item) {
|
||||
let mut iter = self.into_iter();
|
||||
let v1 = iter.next().unwrap();
|
||||
let v2 = iter.next().unwrap();
|
||||
let v3 = iter.next().unwrap();
|
||||
let v4 = iter.next().unwrap();
|
||||
assert!(iter.next().is_none());
|
||||
(v1,v2,v3,v4)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
impl<Collection: IntoIterator>
|
||||
ExpectTuple<(Collection::Item,Collection::Item,Collection::Item,Collection::Item,Collection::Item)>
|
||||
for Collection {
|
||||
fn expect_tuple
|
||||
(self)
|
||||
-> (Collection::Item,Collection::Item,Collection::Item,Collection::Item,Collection::Item) {
|
||||
let mut iter = self.into_iter();
|
||||
let v1 = iter.next().unwrap();
|
||||
let v2 = iter.next().unwrap();
|
||||
let v3 = iter.next().unwrap();
|
||||
let v4 = iter.next().unwrap();
|
||||
let v5 = iter.next().unwrap();
|
||||
assert!(iter.next().is_none());
|
||||
(v1,v2,v3,v4,v5)
|
||||
}
|
||||
}
|
||||
|
21
gui/src/rust/ide/utils/src/vec.rs
Normal file
21
gui/src/rust/ide/utils/src/vec.rs
Normal file
@ -0,0 +1,21 @@
|
||||
//! This module provides utils for the standard Vec<T>.
|
||||
|
||||
/// Extension trait for `Vec<T>` with general-purpose utility functions.
|
||||
pub trait VecExt<T> : AsMut<Vec<T>> {
|
||||
/// Attempts to remove `T` if its `index` is valid. If not, it returns `None`.
|
||||
fn try_remove(&mut self, index:usize) -> Option<T> {
|
||||
let vec = self.as_mut();
|
||||
if index < vec.len() {
|
||||
Some(vec.remove(index))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to remove the first element of `Vec<T>`, returns `None` if its length is zero.
|
||||
fn pop_front(&mut self) -> Option<T> {
|
||||
self.try_remove(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> VecExt<T> for Vec<T> {}
|
@ -186,6 +186,12 @@ macro_rules! log_internal_bug_template_impl {
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! trace {
|
||||
($($toks:tt)*) => {
|
||||
$crate::log_template! {trace $($toks)*}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! info {
|
||||
|
Loading…
Reference in New Issue
Block a user