diff --git a/src/ast/mod.rs b/src/ast/mod.rs index ed14ee3a..8f4d2514 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -8,7 +8,8 @@ use crate::ast::eval_op::*; use crate::ast::Expr::{Apply, Const}; use crate::ast::op::Op; use crate::error::CozoError; -use crate::typing::Typing; +use crate::error::CozoError::ReservedIdent; +use crate::typing::{PrimitiveType, Typing}; use crate::value::Value; mod eval_op; @@ -33,19 +34,23 @@ lazy_static! { }; } +#[derive(Debug, PartialEq)] pub struct Col { pub name: String, pub typ: Typing, pub default: Option>, } +#[derive(Debug, PartialEq)] pub enum TableDef { Node { + is_local: bool, name: String, keys: Vec, cols: Vec, }, Edge { + is_local: bool, src: String, dst: String, name: String, @@ -53,10 +58,17 @@ pub enum TableDef { cols: Vec, }, Columns { + is_local: bool, attached: String, name: String, cols: Vec, }, + Index { + is_local: bool, + name: String, + attached: String, + cols: Vec, + }, } @@ -128,12 +140,13 @@ fn parse_int(s: &str, radix: u32) -> i64 { } #[inline] -fn parse_raw_string(pairs: Pairs) -> Result { - Ok(pairs.into_iter().next().unwrap().as_str().to_string()) +fn parse_raw_string(pair: Pair) -> Result { + Ok(pair.into_inner().into_iter().next().unwrap().as_str().to_string()) } #[inline] -fn parse_quoted_string(pairs: Pairs) -> Result { +fn parse_quoted_string(pair: Pair) -> Result { + let pairs = pair.into_inner().next().unwrap().into_inner(); let mut ret = String::with_capacity(pairs.as_str().len()); for pair in pairs { let s = pair.as_str(); @@ -160,7 +173,8 @@ fn parse_quoted_string(pairs: Pairs) -> Result { #[inline] -fn parse_s_quoted_string(pairs: Pairs) -> Result { +fn parse_s_quoted_string(pair: Pair) -> Result { + let pairs = pair.into_inner().next().unwrap().into_inner(); let mut ret = String::with_capacity(pairs.as_str().len()); for pair in pairs { let s = pair.as_str(); @@ -209,9 +223,9 @@ fn build_expr_primary(pair: Pair) -> Result { Rule::dot_float | Rule::sci_float => Ok(Const(Value::Float(pair.as_str().replace('_', "").parse::()?))), Rule::null => Ok(Const(Value::Null)), Rule::boolean => Ok(Const(Value::Bool(pair.as_str() == "true"))), - Rule::quoted_string => Ok(Const(Value::OwnString(Box::new(parse_quoted_string(pair.into_inner().next().unwrap().into_inner())?)))), - Rule::s_quoted_string => Ok(Const(Value::OwnString(Box::new(parse_s_quoted_string(pair.into_inner().next().unwrap().into_inner())?)))), - Rule::raw_string => Ok(Const(Value::OwnString(Box::new(parse_raw_string(pair.into_inner())?)))), + Rule::quoted_string => Ok(Const(Value::OwnString(Box::new(parse_quoted_string(pair)?)))), + Rule::s_quoted_string => Ok(Const(Value::OwnString(Box::new(parse_s_quoted_string(pair)?)))), + Rule::raw_string => Ok(Const(Value::OwnString(Box::new(parse_raw_string(pair)?)))), _ => { println!("{:#?}", pair); unimplemented!() @@ -228,6 +242,102 @@ pub fn parse_expr_from_str(inp: &str) -> Result { build_expr(expr_tree) } +fn parse_ident(pair: Pair) -> String { + pair.as_str().to_string() +} + +fn build_name_in_def(pair: Pair, forbid_underscore: bool) -> Result { + let inner = pair.into_inner().next().unwrap(); + let name = match inner.as_rule() { + Rule::ident => parse_ident(inner), + Rule::raw_string => parse_raw_string(inner)?, + Rule::s_quoted_string => parse_s_quoted_string(inner)?, + Rule::quoted_string => parse_quoted_string(inner)?, + _ => unreachable!() + }; + if forbid_underscore && name.starts_with('_') { + Err(ReservedIdent) + } else { + Ok(name) + } +} + +fn parse_col_name(pair: Pair) -> Result<(String, bool), CozoError> { + let mut pairs = pair.into_inner(); + let mut is_key = false; + let mut nxt_pair = pairs.next().unwrap(); + if nxt_pair.as_rule() == Rule::key_marker { + is_key = true; + nxt_pair = pairs.next().unwrap(); + } + + Ok((build_name_in_def(nxt_pair, true)?, is_key)) +} + +fn build_col_entry(pair: Pair) -> Result<(Col, bool), CozoError> { + let mut pairs = pair.into_inner(); + let (name, is_key) = parse_col_name(pairs.next().unwrap())?; + Ok((Col { + name, + typ: Typing::Primitive(PrimitiveType::Int), + default: None + }, is_key)) +} + +fn build_col_defs(pair: Pair) -> Result<(Vec, Vec), CozoError> { + let mut keys = vec![]; + let mut cols = vec![]; + for pair in pair.into_inner() { + let (col, is_key) = build_col_entry(pair)?; + if is_key { + keys.push(col) + } else { + cols.push(col) + } + } + + Ok((keys, cols)) +} + +fn build_node_def(pair: Pair, is_local: bool) -> Result { + let mut inner = pair.into_inner(); + let name = build_name_in_def(inner.next().unwrap(), true)?; + let (keys, cols) = build_col_defs(inner.next().unwrap())?; + Ok(TableDef::Node { + is_local, + name, + keys, + cols, + }) +} + +pub fn build_statements(pairs: Pairs) -> Result, CozoError> { + let mut ret = vec![]; + for pair in pairs { + match pair.as_rule() { + r @ (Rule::global_def | Rule::local_def) => { + let inner = pair.into_inner().next().unwrap(); + let is_local = r == Rule::local_def; + // println!("{:?} {:?}", r, inner.as_rule()); + match inner.as_rule() { + Rule::node_def => { + ret.push(build_node_def(inner, is_local)?); + } + _ => todo!() + } + } + Rule::EOI => {} + _ => unreachable!() + } + } + Ok(ret) +} + +pub fn build_statements_from_str(inp: &str) -> Result, CozoError> { + let expr_tree = Parser::parse(Rule::file, inp)?; + build_statements(expr_tree) +} + #[cfg(test)] mod tests { use super::*; @@ -269,4 +379,16 @@ mod tests { println!("{:#?}", parse_expr_from_str("null || true").unwrap().eval().unwrap()); println!("{:#?}", parse_expr_from_str("true && null").unwrap().eval().unwrap()); } + + #[test] + fn definitions() { + println!("{:#?}", build_statements_from_str(r#" + local node "Person" { + *id: Int, + name: String, + email: ?String, + habits: [String] + } + "#).unwrap()); + } } \ No newline at end of file diff --git a/src/error.rs b/src/error.rs index a44f5dd9..ce8c302e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -12,6 +12,9 @@ pub enum CozoError { #[error("Type mismatch")] TypeError, + #[error("Reserved identifier")] + ReservedIdent, + #[error(transparent)] ParseInt(#[from] std::num::ParseIntError), diff --git a/src/grammar.pest b/src/grammar.pest index b59ac75f..a7c2f1c3 100644 --- a/src/grammar.pest +++ b/src/grammar.pest @@ -1,4 +1,4 @@ -file = {SOI ~ ident ~ EOI} +file = _{SOI ~ statement* ~ EOI} // whitespace @@ -125,4 +125,33 @@ dict_entry = _{ spreading | dict_accessor | dict_pair } dict_accessor = { ident? ~ ("." ~ ident)+ } dict_pair = {(ident | string) ~ ":" ~ expr} -scoped_dict = { ident ~ dict } \ No newline at end of file +scoped_dict = { ident ~ dict } + +name_in_def = {(ident | string)} +col_entry = { col_name ~ ":" ~ typing ~ ("=" ~ expr)? } + +col_name = { key_marker? ~ name_in_def } +key_marker = { "*" } + +typing = { nullable_marker? ~ (simple_type | tuple_type | list_type ) } +nullable_marker = {"?"} +simple_type = {ident} +tuple_type = {"("~ (typing ~ ",")+ ~")"} +list_type = {"[" ~ typing ~ "]"} + +cols_def = { "{" ~ col_entry ~ ("," ~ col_entry)* ~ ","? ~ "}" } + +col_list = {"(" ~ name_in_def ~ ("," ~ name_in_def)* ~ ","? ~ ")"} + +node_def = { "node" ~ name_in_def ~ cols_def } +columns_def = { "columns" ~ name_in_def ~ ":" ~ name_in_def ~ cols_def } +edge_def = { "edge" ~ + "[" ~ name_in_def ~ "]" ~ "-" ~ "(" ~ name_in_def ~ ")" ~ "->" ~ "[" ~ name_in_def ~ "]" + ~ cols_def? } +index_def = { "index" ~ (name_in_def ~ ":")? ~ name_in_def ~ col_list } +struct_def = { "struct" ~ name_in_def ~ cols_def } + +definition = _{ node_def | columns_def | edge_def | struct_def | index_def } +global_def = { "create" ~ definition } +local_def = { "local" ~ definition } +statement = _{ global_def | local_def } \ No newline at end of file