The optional Expression Id was added to SpanTree, to allow reading type information by views.

Original commit: b85eeba5aa
This commit is contained in:
Adam Obuchowicz 2020-06-24 09:35:10 +02:00 committed by GitHub
parent cb6a16d402
commit 87326a2f3b
10 changed files with 242 additions and 111 deletions

View File

@ -127,7 +127,7 @@ commands.build.rust = async function(argv) {
console.log('Checking the resulting WASM size.')
let stats = fss.statSync(paths.dist.wasm.mainOptGz)
let limit = 3.32
let limit = 3.33
let size = Math.round(100 * stats.size / 1024 / 1024) / 100
if (size > limit) {
throw(`Output file size exceeds the limit (${size}MB > ${limit}MB).`)

View File

@ -75,14 +75,17 @@ impl 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:impl Into<Span>, id:Id) {
self.vec.push((span.into(),id));
}
/// Generate random Uuid for span.
pub fn generate(&mut self, span:impl Into<Span>) {
self.vec.push((span.into(),Uuid::new_v4()));
}
}
// ==============
// === Errors ===
// ==============

View File

@ -2,6 +2,7 @@
use crate::prelude::*;
use crate::Id;
use crate::Infix;
use crate::SectionLeft;
use crate::SectionRight;
@ -129,6 +130,8 @@ pub struct GeneralizedInfix {
pub opr : Operator,
/// Right operand, if present.
pub right : Operand,
/// Infix id.
pub id : Option<Id>,
}
/// A structure used for GeneralizedInfix construction which marks operands as _target_ and
@ -144,23 +147,28 @@ impl GeneralizedInfix {
/// Tries interpret given AST node as GeneralizedInfix. Returns None, if Ast is not any kind of
/// application on infix operator.
pub fn try_new(ast:&Ast) -> Option<GeneralizedInfix> {
let id = ast.id;
match ast.shape().clone() {
Shape::Infix(infix) => Some(GeneralizedInfix{
id,
left : make_operand (infix.larg,infix.loff),
opr : make_operator(&infix.opr)?,
right : make_operand (infix.rarg,infix.roff),
}),
Shape::SectionLeft(left) => Some(GeneralizedInfix{
id,
left : make_operand (left.arg,left.off),
opr : make_operator(&left.opr)?,
right : None,
}),
Shape::SectionRight(right) => Some(GeneralizedInfix{
id,
left : None,
opr : make_operator(&right.opr)?,
right : make_operand (right.arg,right.off),
}),
Shape::SectionSides(sides) => Some(GeneralizedInfix{
id,
left : None,
opr : make_operator(&sides.opr)?,
right : None,
@ -170,13 +178,13 @@ impl GeneralizedInfix {
}
/// Constructor with operands marked as target and argument.
pub fn new_from_operands(operands:MarkedOperands, opr:Operator) -> Self {
pub fn new_from_operands(operands:MarkedOperands, opr:Operator, id:Option<Id>) -> Self {
match assoc(&opr) {
Assoc::Left => GeneralizedInfix {opr,
Assoc::Left => GeneralizedInfix {opr,id,
left : operands.target,
right : operands.argument,
},
Assoc::Right => GeneralizedInfix {opr,
Assoc::Right => GeneralizedInfix {opr,id,
left : operands.argument,
right : operands.target,
},
@ -185,7 +193,7 @@ impl GeneralizedInfix {
/// Convert to AST node.
pub fn into_ast(self) -> Ast {
match (self.left,self.right) {
let ast:Ast = match (self.left,self.right) {
(Some(left),Some(right)) => Infix{
larg : left.arg,
loff : left.offset,
@ -206,6 +214,11 @@ impl GeneralizedInfix {
(None,None) => SectionSides {
opr : self.opr.into()
}.into()
};
if let Some(id) = self.id{
ast.with_id(id)
} else {
ast
}
}
@ -247,6 +260,7 @@ impl GeneralizedInfix {
let rest = ChainElement {offset,
operator : self.opr.clone(),
operand : self.argument_operand(),
infix_id : self.id,
};
let target_subtree_infix = target.clone().and_then(|arg| {
@ -345,11 +359,12 @@ impl Chain {
let mut operand = Some(operand);
let operator = self.operator.clone_ref();
let before_target = at_index == 0;
let infix_id:Option<Id> = None;
if before_target {
std::mem::swap(&mut operand, &mut self.target);
self.args.insert(0,ChainElement{operator,operand,offset})
self.args.insert(0,ChainElement{operator,operand,offset,infix_id})
} else {
self.args.insert(at_index-1,ChainElement{operator,operand,offset})
self.args.insert(at_index-1,ChainElement{operator,operand,offset,infix_id})
}
}
@ -380,7 +395,8 @@ impl Chain {
let operator = element.operator;
let argument = element.operand;
let operands = MarkedOperands{target,argument};
let new_infix = GeneralizedInfix::new_from_operands(operands,operator);
let id = element.infix_id;
let new_infix = GeneralizedInfix::new_from_operands(operands,operator,id);
let new_with_offset = ArgWithOffset {
arg : new_infix.into_ast(),
offset : element.offset,
@ -429,6 +445,8 @@ pub struct ChainElement {
pub operand : Operand,
/// Offset between this operand and the next operator.
pub offset : usize,
/// Id of infix AST which applies this operand.
pub infix_id : Option<Id>,
}
impl ChainElement {

View File

@ -2,22 +2,49 @@
use crate::prelude::*;
use crate::Ast;
use crate::{Ast, TokenConsumer};
use crate::Id;
use crate::crumbs::Located;
use crate::crumbs::PrefixCrumb;
use crate::HasTokens;
use crate::known;
use crate::Prefix;
use crate::Shifted;
use utils::vec::VecExt;
// ================
// === Argument ===
// ================
/// Struct representing an element of a Prefix Chain: an argument applied over the function.
#[derive(Clone,Debug)]
pub struct Argument {
/// An argument ast with offset between it and previous arg or function.
pub sast : Shifted<Ast>,
/// The id of Prefix AST of this argument application.
pub prefix_id : Option<Id>,
}
impl HasTokens for Argument {
fn feed_to(&self, consumer: &mut impl TokenConsumer) {
self.sast.feed_to(consumer)
}
}
// ====================
// === Prefix Chain ===
// ====================
/// Result of flattening a sequence of prefix applications.
#[derive(Clone,Debug)]
pub struct Chain {
/// The function (initial application target)
pub func : Ast,
/// Subsequent arguments applied over the function.
pub args : Vec<Shifted<Ast>>,
pub args : Vec<Argument>,
}
impl Chain {
@ -25,12 +52,14 @@ impl Chain {
/// App(App(a,b),c) into flat list where first element is the function and
/// then arguments are placed: `{func:a, args:[b,c]}`.
pub fn new(ast:&known::Prefix) -> Chain {
fn run(ast:&known::Prefix, mut acc: &mut Vec<Shifted<Ast>>) -> Ast {
fn run(ast:&known::Prefix, mut acc: &mut Vec<Argument>) -> Ast {
let func = match known::Prefix::try_from(&ast.func) {
Ok(lhs_app) => run(&lhs_app, &mut acc),
_ => ast.func.clone(),
};
acc.push(Shifted{wrapped:ast.arg.clone(),off:ast.off});
let sast = Shifted{wrapped:ast.arg.clone(),off:ast.off};
let prefix_id = ast.id();
acc.push(Argument{sast,prefix_id});
func
}
@ -54,7 +83,9 @@ impl Chain {
// Case like `+ a b`
let func = section.opr.clone();
let right_chain = Chain::new_non_strict(&section.arg);
let mut args = vec![Shifted{wrapped:right_chain.func, off:section.off}];
let sast = Shifted{wrapped:right_chain.func, off:section.off};
let prefix_id = section.id();
let mut args = vec![Argument{sast,prefix_id}];
args.extend(right_chain.args);
Chain {func,args}
} else {
@ -87,7 +118,7 @@ impl Chain {
let mut i = 0;
self.args.iter().map(move |arg| {
i += 1;
Located::new(&func_crumbs[i..],&arg.wrapped)
Located::new(&func_crumbs[i..],&arg.sast.wrapped)
})
}
@ -96,11 +127,11 @@ impl Chain {
pub fn fold_arg(&mut self) {
if let Some(arg) = self.args.pop_front() {
let new_prefix = Prefix{
arg : arg.wrapped,
arg : arg.sast.wrapped,
func : self.func.clone_ref(),
off : arg.off,
off : arg.sast.off,
};
self.func = new_prefix.into();
self.func = Ast::new(new_prefix,arg.prefix_id);
}
}
@ -120,6 +151,7 @@ mod tests {
use super::*;
use utils::test::ExpectTuple;
use uuid::Uuid;
#[test]
fn prefix_chain() {
@ -127,13 +159,15 @@ mod tests {
let b = Ast::var("b");
let c = Ast::var("c");
let a_b = Ast::prefix(a.clone(),b.clone());
let a_b_c = Ast::prefix(a_b.clone(),c.clone());
let a_b = Ast::prefix(a.clone(),b.clone()).with_id(Uuid::new_v4());
let a_b_c = Ast::prefix(a_b.clone(),c.clone()).with_id(Uuid::new_v4());
let chain = Chain::try_new(&a_b_c).unwrap();
assert_eq!(chain.func, a);
assert_eq!(chain.args[0].wrapped, b);
assert_eq!(chain.args[1].wrapped, c);
assert_eq!(chain.args[0].sast.wrapped, b);
assert_eq!(chain.args[1].sast.wrapped, c);
assert_eq!(chain.args[0].prefix_id, a_b.id);
assert_eq!(chain.args[1].prefix_id, a_b_c.id);
let (arg1,arg2) = chain.enumerate_args().expect_tuple();
assert_eq!(arg1.item, &b);

View File

@ -97,9 +97,14 @@ impl Parser {
}
/// Program is expected to be single non-empty line module. The line's AST is
/// returned. Panics otherwise.
/// returned. Panics otherwise. The program is parsed with empty IdMap.
pub fn parse_line(&self, program:impl Str) -> FallibleResult<Ast> {
let module = self.parse_module(program,default())?;
self.parse_line_with_id_map(program,default())
}
/// Program is expected to be single non-empty line module. The line's AST is returned. Panics
/// otherwise.
pub fn parse_line_with_id_map(&self, program:impl Str, id_map:IdMap) -> FallibleResult<Ast> {
let module = self.parse_module(program,id_map)?;
let mut lines = module.lines.clone().into_iter().filter_map(|line| {
line.elem

View File

@ -130,7 +130,10 @@ impl<'a> Implementation for node::Ref<'a> {
infix.into_ast()
} else {
let mut prefix = ast::prefix::Chain::new_non_strict(ast);
let item = Shifted{wrapped:new, off:DEFAULT_OFFSET};
let item = ast::prefix::Argument{
sast : Shifted{wrapped:new, off:DEFAULT_OFFSET},
prefix_id : None,
};
match ins_type {
BeforeTarget => prefix.args.insert(0,item),
AfterTarget => prefix.args.insert(1,item),

View File

@ -22,8 +22,9 @@ pub trait Builder : Sized {
fn add_child
(self, offset:usize, len:usize, kind:node::Kind, crumbs:impl IntoCrumbs) -> ChildBuilder<Self> {
let node = Node {kind,
size: Size::new(len),
children : vec![]
size : Size::new(len),
children : vec![],
expression_id : None,
};
let child = node::Child { node,
offset : Size::new(offset),
@ -50,6 +51,12 @@ pub trait Builder : Sized {
self.node_being_built().children.push(child);
self
}
/// Set expression id for this node.
fn set_expression_id(mut self, id:ast::Id) -> Self {
self.node_being_built().expression_id = Some(id);
self
}
}
@ -74,6 +81,7 @@ impl TreeBuilder {
kind : node::Kind::Root,
size : Size::new(len),
children : vec![],
expression_id : None,
}
}
}

View File

@ -110,14 +110,15 @@ impl SpanTreeGenerator for Ast {
ast::prefix::Chain::try_new(self).unwrap().generate_node(kind),
// Lambdas should fall in _ case, because we don't want to create subports for
// them
ast::Shape::Match(ast) if ast::macros::as_lambda_match(self).is_none() =>
ast.generate_node(kind),
ast::Shape::Ambiguous(ast) =>
ast.generate_node(kind),
ast::Shape::Match(_) if ast::macros::as_lambda_match(self).is_none() =>
ast::known::Match::try_new(self.clone_ref()).unwrap().generate_node(kind),
ast::Shape::Ambiguous(_) =>
ast::known::Ambiguous::try_new(self.clone_ref()).unwrap().generate_node(kind),
_ => {
let size = Size::new(self.len());
let children = default();
Ok(Node {kind,size,children})
let expression_id = self.id;
Ok(Node {kind,size,children,expression_id})
},
}
}
@ -177,6 +178,7 @@ impl SpanTreeGenerator for ast::opr::Chain {
kind : if is_last {kind} else {node::Kind::Chained},
size : gen.current_offset,
children : gen.children,
expression_id : elem.infix_id,
}, elem.offset))
})?;
Ok(node)
@ -201,16 +203,17 @@ impl SpanTreeGenerator for ast::prefix::Chain {
let mut gen = ChildGenerator::default();
gen.add_node(vec![Func.into()],node);
gen.spacing(arg.off);
gen.spacing(arg.sast.off);
if let node::Kind::Target {..} = arg_kind {
gen.generate_empty_node(InsertType::BeforeTarget);
}
gen.generate_ast_node(Located::new(Arg,arg.wrapped.clone_ref()), arg_kind)?;
gen.generate_ast_node(Located::new(Arg,arg.sast.wrapped.clone_ref()), arg_kind)?;
gen.generate_empty_node(InsertType::Append);
Ok(Node {
kind : if is_last {kind} else {node::Kind::Chained},
size : gen.current_offset,
children : gen.children,
expression_id : arg.prefix_id,
})
})
}
@ -219,7 +222,7 @@ impl SpanTreeGenerator for ast::prefix::Chain {
// === Match ===
impl SpanTreeGenerator for ast::Match<Ast> {
impl SpanTreeGenerator for ast::known::Match {
fn generate_node(&self, kind:node::Kind) -> FallibleResult<Node> {
let is_removable = false;
let children_kind = node::Kind::Argument {is_removable};
@ -241,6 +244,7 @@ impl SpanTreeGenerator for ast::Match<Ast> {
Ok(Node {kind,
size : gen.current_offset,
children : gen.children,
expression_id : self.id(),
})
}
}
@ -263,7 +267,7 @@ fn generate_children_from_segment
// === Ambiguous ==
impl SpanTreeGenerator for ast::Ambiguous<Ast> {
impl SpanTreeGenerator for ast::known::Ambiguous {
fn generate_node(&self, kind:node::Kind) -> FallibleResult<Node> {
let mut gen = ChildGenerator::default();
let first_segment_index = 0;
@ -275,6 +279,7 @@ impl SpanTreeGenerator for ast::Ambiguous<Ast> {
Ok(Node{kind,
size : gen.current_offset,
children : gen.children,
expression_id : self.id(),
})
}
}
@ -317,16 +322,45 @@ mod test {
use parser::Parser;
use wasm_bindgen_test::wasm_bindgen_test;
use wasm_bindgen_test::wasm_bindgen_test_configure;
use ast::IdMap;
wasm_bindgen_test_configure!(run_in_browser);
/// A helper function which removes information about expression id from thw tree rooted at
/// `node`.
///
/// It is used in tests. Because parser can assign id as he pleases, therefore to keep tests
/// cleaner the expression ids are removed before comparing trees.
fn clear_expression_ids(node:&mut Node) {
node.expression_id = None;
for child in &mut node.children {
clear_expression_ids(&mut child.node);
}
}
#[wasm_bindgen_test]
fn generating_span_tree() {
let parser = Parser::new_or_panic();
let ast = parser.parse_line("2 + foo bar - 3").unwrap();
let tree = ast.generate_tree().unwrap();
let is_removable = false;
let mut id_map = IdMap::default();
id_map.generate(0..15);
id_map.generate(0..11);
id_map.generate(12..13);
id_map.generate(14..15);
id_map.generate(4..11);
let ast = parser.parse_line_with_id_map("2 + foo bar - 3",id_map.clone()).unwrap();
let mut tree = ast.generate_tree().unwrap();
// Check the expression ids we defined:
for id_map_entry in id_map.vec {
let (span,id) = id_map_entry;
let node = tree.root_ref().find_by_span(&span);
assert!(node.is_some(), "Node with span {} not found", span);
assert_eq!(node.unwrap().node.expression_id, Some(id));
}
// Check the other fields:
clear_expression_ids(&mut tree.root);
let is_removable = false;
let expected = TreeBuilder::new(15)
.add_empty_child(0,BeforeTarget)
.add_child(0,11,Target{is_removable},InfixCrumb::LeftOperand)
@ -355,9 +389,10 @@ mod test {
fn generate_span_tree_with_chains() {
let parser = Parser::new_or_panic();
let ast = parser.parse_line("2 + 3 + foo bar baz 13 + 5").unwrap();
let tree = ast.generate_tree().unwrap();
let is_removable = true;
let mut tree = ast.generate_tree().unwrap();
clear_expression_ids(&mut tree.root);
let is_removable = true;
let expected = TreeBuilder::new(26)
.add_child(0,22,Chained,InfixCrumb::LeftOperand)
.add_child(0,5,Chained,InfixCrumb::LeftOperand)
@ -397,9 +432,10 @@ mod test {
fn generating_span_tree_from_right_assoc_operator() {
let parser = Parser::new_or_panic();
let ast = parser.parse_line("1,2,3").unwrap();
let tree = ast.generate_tree().unwrap();
let is_removable = true;
let mut tree = ast.generate_tree().unwrap();
clear_expression_ids(&mut tree.root);
let is_removable = true;
let expected = TreeBuilder::new(5)
.add_empty_child(0,Append)
.add_leaf (0,1,Argument{is_removable},InfixCrumb::LeftOperand)
@ -423,9 +459,10 @@ mod test {
// The star makes `SectionSides` ast being one of the parameters of + chain. First + makes
// SectionRight, and last + makes SectionLeft.
let ast = parser.parse_line("+ * + + 2 +").unwrap();
let tree = ast.generate_tree().unwrap();
let is_removable = true;
let mut tree = ast.generate_tree().unwrap();
clear_expression_ids(&mut tree.root);
let is_removable = true;
let expected = TreeBuilder::new(11)
.add_child(0,9,Chained,SectionLeftCrumb::Arg)
.add_child(0,5,Chained,InfixCrumb::LeftOperand)
@ -457,9 +494,10 @@ mod test {
fn generating_span_tree_from_right_assoc_section() {
let parser = Parser::new_or_panic();
let ast = parser.parse_line(",2,").unwrap();
let tree = ast.generate_tree().unwrap();
let is_removable = true;
let mut tree = ast.generate_tree().unwrap();
clear_expression_ids(&mut tree.root);
let is_removable = true;
let expected = TreeBuilder::new(3)
.add_empty_child(0,Append)
.add_leaf (0,1,Operation,SectionRightCrumb::Opr)
@ -479,10 +517,19 @@ mod test {
use PatternMatchCrumb::*;
let parser = Parser::new_or_panic();
let ast = parser.parse_line("if foo then (a + b) x else ()").unwrap();
let tree = ast.generate_tree().unwrap();
let is_removable = false;
let mut id_map = IdMap::default();
id_map.generate(0..29);
let expression = "if foo then (a + b) x else ()";
let ast = parser.parse_line_with_id_map(expression,id_map.clone()).unwrap();
let mut tree = ast.generate_tree().unwrap();
// Check if expression id is set
let (_,expected_id) = id_map.vec.first().unwrap();
assert_eq!(tree.root_ref().expression_id,Some(*expected_id));
// Check the other fields
clear_expression_ids(&mut tree.root);
let is_removable = false;
let if_then_else_cr = vec![Seq { right: false }, Or, Build];
let parens_cr = vec![Seq { right: false }, Or, Or, Build];
let segment_body_crumbs = |index:usize, pattern_crumb:&Vec<PatternMatchCrumb>| {
@ -516,11 +563,19 @@ mod test {
#[wasm_bindgen_test]
fn generating_span_tree_from_ambiguous_macros() {
let parser = Parser::new_or_panic();
let ast = parser.parse_line("(4").unwrap();
let tree = ast.generate_tree().unwrap();
let mut id_map = IdMap::default();
id_map.generate(0..2);
let ast = parser.parse_line_with_id_map("(4",id_map.clone()).unwrap();
let mut tree = ast.generate_tree().unwrap();
// Check the expression id:
let (_,expected_id) = id_map.vec.first().unwrap();
assert_eq!(tree.root_ref().expression_id,Some(*expected_id));
// Check the other fields:
clear_expression_ids(&mut tree.root);
let is_removable = false;
let crumb = AmbiguousCrumb{index:0, field:AmbiguousSegmentCrumb::Body};
let expected = TreeBuilder::new(2)
.add_leaf(1,1,Argument {is_removable},crumb)
.build();
@ -532,9 +587,10 @@ mod test {
fn generating_span_tree_for_lambda() {
let parser = Parser::new_or_panic();
let ast = parser.parse_line("foo a-> b + c").unwrap();
let tree = ast.generate_tree().unwrap();
let is_removable = false;
let mut tree = ast.generate_tree().unwrap();
clear_expression_ids(&mut tree.root);
let is_removable = false;
let expected = TreeBuilder::new(13)
.add_leaf(0,3,Operation,PrefixCrumb::Func)
.add_empty_child(4,BeforeTarget)

View File

@ -86,10 +86,11 @@ impl SpanTree {
impl Default for SpanTree {
fn default() -> Self {
let expression_id = None;
let kind = node::Kind::Root;
let size = default();
let children = default();
let root = Node {kind,size,children};
let root = Node {kind,size,children,expression_id};
Self {root}
}
}

View File

@ -59,7 +59,8 @@ pub enum InsertType {BeforeTarget,AfterTarget,Append}
// === Errors ===
#[allow(missing_docs)]
#[fail(display = "The crumb `{}` is invalid, only {} children present. Traversed crumbs: {:?}.", crumb,count,context)]
#[fail(display = "The crumb `{}` is invalid, only {} children present. Traversed crumbs: {:?}.",
crumb,count,context)]
#[derive(Debug,Fail,Clone)]
pub struct InvalidCrumb {
/// Crumb that was attempted.
@ -93,6 +94,7 @@ pub struct Node {
pub kind : Kind,
pub size : Size,
pub children : Vec<Child>,
pub expression_id : Option<ast::Id>,
}
impl Node {
@ -102,6 +104,7 @@ impl Node {
kind : Kind::Empty(insert_type),
size : Size::new(0),
children : Vec::new(),
expression_id : None,
}
}