From 8408d625390f7eae348636563398d250e40379ac Mon Sep 17 00:00:00 2001 From: Ziyang Hu Date: Tue, 3 May 2022 17:57:59 +0800 Subject: [PATCH] scoped dict and spreading --- src/db/eval.rs | 48 ++++++++++++++++++++++++++++++++--- src/grammar.pest | 4 +-- src/relation/value.rs | 58 ++++++++++++++++++++++++++++++++++++++----- 3 files changed, 99 insertions(+), 11 deletions(-) diff --git a/src/db/eval.rs b/src/db/eval.rs index 236e4912..92c1d1ef 100644 --- a/src/db/eval.rs +++ b/src/db/eval.rs @@ -5,7 +5,7 @@ use cozorocks::{SlicePtr}; use crate::db::engine::{Session}; use crate::relation::tuple::{OwnTuple, Tuple}; use crate::relation::typing::Typing; -use crate::relation::value::{METHOD_CONCAT, Value}; +use crate::relation::value::{Value}; use crate::error::{CozoError, Result}; use crate::error::CozoError::LogicError; use crate::relation::data::DataKind; @@ -13,7 +13,8 @@ use crate::parser::{Rule}; use crate::parser::text_identifier::build_name_in_def; use crate::relation::value; -/// layouts for sector 0 +/// # layouts for sector 0 +/// /// `[Null]`: stores information about table_ids /// `[Text, Int]`: contains definable data and depth info /// `[Int, Text]`: inverted index for depth info @@ -358,6 +359,7 @@ impl<'s> Session<'s> { value::METHOD_IS_NULL => self.is_null_values(args, params, table_bindings)?, value::METHOD_NOT_NULL => self.not_null_values(args, params, table_bindings)?, value::METHOD_CONCAT => self.concat_values(args, params, table_bindings)?, + value::METHOD_MERGE => self.merge_values(args, params, table_bindings)?, _ => { todo!() } }) } @@ -779,7 +781,47 @@ impl<'s> Session<'s> { if !cur_ret.is_empty() { total_ret.push(cur_ret.into()); } - Ok((false, Value::Apply(METHOD_CONCAT.into(), total_ret))) + Ok((false, Value::Apply(value::METHOD_CONCAT.into(), total_ret))) + } + } + fn merge_values<'a>(&self, args: Vec>, params: &BTreeMap>, + table_bindings: &BTreeMap) -> Result<(bool, Value<'a>)> { + let mut total_ret = vec![]; + let mut cur_ret = BTreeMap::new(); + let mut evaluated = true; + for val in args.into_iter() { + let (ev, val) = self.partial_eval(val, params, table_bindings)?; + evaluated = ev && evaluated; + match val { + Value::Dict(d) => { + if cur_ret.is_empty() { + cur_ret = d; + } else { + cur_ret.extend(d); + } + } + v @ (Value::Variable(_) | + Value::Apply(_, _) | + Value::FieldAccess(_, _) | + Value::IdxAccess(_, _)) => { + if !cur_ret.is_empty() { + total_ret.push(Value::Dict(cur_ret)); + cur_ret = BTreeMap::new(); + } + total_ret.push(v); + } + _ => { + return Err(LogicError("Cannot concat incompatible types".to_string())); + } + } + } + if total_ret.is_empty() { + Ok((evaluated, cur_ret.into())) + } else { + if !cur_ret.is_empty() { + total_ret.push(cur_ret.into()); + } + Ok((false, Value::Apply(value::METHOD_MERGE.into(), total_ret))) } } fn and_values<'a>(&self, args: Vec>, params: &BTreeMap>, diff --git a/src/grammar.pest b/src/grammar.pest index 1c166636..03a3bc4c 100644 --- a/src/grammar.pest +++ b/src/grammar.pest @@ -122,8 +122,8 @@ list_el = _{spreading | expr} spreading = {"..." ~ term} dict = { "{" ~ (dict_entry ~ ",")* ~ dict_entry? ~ "}"} -dict_entry = _{ spreading | dict_accessor | dict_pair } -dict_accessor = { ident? ~ ("." ~ ident)+ } +dict_entry = _{ spreading | dict_pair | scoped_accessor } +scoped_accessor = { ident } dict_pair = {(ident | string) ~ ":" ~ expr} scoped_dict = { ident ~ dict } diff --git a/src/relation/value.rs b/src/relation/value.rs index 9c550d62..34ae2ff2 100644 --- a/src/relation/value.rs +++ b/src/relation/value.rs @@ -88,18 +88,22 @@ impl TryFrom for Tag { #[derive(Debug, Clone, PartialEq, Ord, PartialOrd, Eq)] pub enum Value<'a> { + // evaluated Null, Bool(bool), Int(i64), Float(OrderedFloat), Uuid(Uuid), Text(Cow<'a, str>), + // maybe evaluated List(Vec>), Dict(BTreeMap, Value<'a>>), + // not evaluated Variable(Cow<'a, str>), Apply(Cow<'a, str>, Vec>), FieldAccess(Cow<'a, str>, Box>), IdxAccess(usize, Box>), + // cannot exist EndSentinel, } @@ -354,6 +358,7 @@ pub const OP_MINUS: &str = "--"; pub const METHOD_IS_NULL: &str = "is_null"; pub const METHOD_NOT_NULL: &str = "not_null"; pub const METHOD_CONCAT: &str = "concat"; +pub const METHOD_MERGE: &str = "merge"; fn build_expr_infix<'a>(lhs: Result>, op: Pair, rhs: Result>) -> Result> { @@ -443,7 +448,8 @@ fn build_expr_primary(pair: Pair) -> Result { Rule::spreading => { let el = p.into_inner().next().unwrap(); let to_concat = build_expr_primary(el)?; - if !matches!(to_concat, Value::List(_) | Value::Variable(_)) { + if !matches!(to_concat, Value::List(_) | Value::Variable(_) | + Value::IdxAccess(_, _) | Value:: FieldAccess(_, _) | Value::Apply(_, _)) { return Err(CozoError::LogicError("Cannot spread".to_string())); } if !collected.is_empty() { @@ -461,20 +467,50 @@ fn build_expr_primary(pair: Pair) -> Result { if !collected.is_empty() { spread_collected.push(Value::List(collected)); } - Ok(Value::Apply("concat".into(), spread_collected)) + Ok(Value::Apply(METHOD_CONCAT.into(), spread_collected)) } Rule::dict => { - Ok(pair.into_inner().map(|p| { + let mut spread_collected = vec![]; + let mut collected = BTreeMap::new(); + for p in pair.into_inner() { match p.as_rule() { Rule::dict_pair => { let mut inner = p.into_inner(); let name = parse_string(inner.next().unwrap())?; let val = build_expr_primary(inner.next().unwrap())?; - Ok((name.into(), val)) + collected.insert(name.into(), val); } - _ => todo!() + Rule::scoped_accessor => { + let name = parse_string(p.into_inner().next().unwrap())?; + let val = Value::FieldAccess(name.clone().into(), + Value::Variable("_".into()).into()); + collected.insert(name.into(), val); + } + Rule::spreading => { + let el = p.into_inner().next().unwrap(); + let to_concat = build_expr_primary(el)?; + if !matches!(to_concat, Value::Dict(_) | Value::Variable(_) | + Value::IdxAccess(_, _) | Value:: FieldAccess(_, _) | Value::Apply(_, _)) { + return Err(CozoError::LogicError("Cannot spread".to_string())); + } + if !collected.is_empty() { + spread_collected.push(Value::Dict(collected)); + collected = BTreeMap::new(); + } + spread_collected.push(to_concat); + } + _ => unreachable!() } - }).collect::, Value>>>()?.into()) + } + + if spread_collected.is_empty() { + return Ok(Value::Dict(collected)); + } + + if !collected.is_empty() { + spread_collected.push(Value::Dict(collected)); + } + Ok(Value::Apply(METHOD_MERGE.into(), spread_collected)) } Rule::param => { Ok(Value::Variable(pair.as_str().into())) @@ -535,4 +571,14 @@ mod tests { assert_eq!(parse_expr_from_str(r#"'"x"'"#).unwrap(), Value::Text(r##""x""##.into())); assert_eq!(parse_expr_from_str(r#####"r###"x"yz"###"#####).unwrap(), (Value::Text(r##"x"yz"##.into()))); } + + #[test] + fn complex_cases() -> Result<()> { + println!("{}", parse_expr_from_str("{}")?); + println!("{}", parse_expr_from_str("{b:1,a,c:2,d,...e,}")?); + println!("{}", parse_expr_from_str("{...a,...b,c:1,d:2,...e,f:3}")?); + println!("{}", parse_expr_from_str("[]")?); + println!("{}", parse_expr_from_str("[...a,...b,1,2,...e,3]")?); + Ok(()) + } } \ No newline at end of file