Original commit: cf01ebf061
This commit is contained in:
Michał Wawrzyniec Urbańczyk 2020-03-27 23:59:02 +01:00 committed by GitHub
parent 6552176247
commit fd269d2457
24 changed files with 2088 additions and 307 deletions

View File

@ -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"

View File

@ -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" }

View 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);
}
}

View File

@ -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
}
}
// ===============

View File

@ -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);
}
}

View File

@ -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>;

View File

@ -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 ===
// =====================

View File

@ -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());
}
}

View 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;

View File

@ -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 {

View File

@ -15,6 +15,8 @@
#![warn(missing_debug_implementations)]
pub mod api;
pub mod test_utils;
mod jsclient;
mod wsclient;

View 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 {}

View File

@ -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);
};
}

View File

@ -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);
})
}
}

View File

@ -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 ===
// =============

View File

@ -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;

View File

@ -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"]));
}
}

View File

@ -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)
}
}

View File

@ -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;

View File

@ -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::*;

View File

@ -16,3 +16,4 @@ pub mod channel;
pub mod env;
pub mod fail;
pub mod test;
pub mod vec;

View File

@ -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)
}
}

View 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> {}

View File

@ -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 {