From 62b5dd5179c86ac582149239e04df69fb8f8d842 Mon Sep 17 00:00:00 2001 From: Felipe705x Date: Sun, 22 Feb 2026 16:48:53 -0300 Subject: [PATCH 1/2] refactor: root-level files --- src/ast.rs | 13 ++ src/parser.rs | 629 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 642 insertions(+) create mode 100644 src/ast.rs create mode 100644 src/parser.rs diff --git a/src/ast.rs b/src/ast.rs new file mode 100644 index 0000000..e6849ef --- /dev/null +++ b/src/ast.rs @@ -0,0 +1,13 @@ +mod descriptor; +mod expr; +mod label; +mod pattern; +mod types; +mod var; + +pub use descriptor::{Descriptor, DescriptorType, PropertyType}; +pub use expr::{BinOpKind, Constant, Expr, UnOpKind}; +pub use label::LabelType; +pub use pattern::{EdgeDirection, PathPattern, Quantifier}; +pub use types::{BaseType, SimpleType}; +pub use var::Var; diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..4568d9e --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,629 @@ +use lalrpop_util::lalrpop_mod; + +use crate::ast::PathPattern; + +lalrpop_mod!(grammar); + +/// Error type for parsing operations. +pub type ParseError<'input> = + lalrpop_util::ParseError, &'static str>; + +/// Parse a path pattern from a string. +/// +/// # Example +/// ``` +/// use fppc::parser::parse; +/// let pattern = parse("(x:Person)-[:KNOWS]->(y)").unwrap(); +/// ``` +pub fn parse(input: &str) -> Result> { + grammar::PathPatternParser::new().parse(input) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::{ + BaseType, BinOpKind, Constant, Descriptor, DescriptorType, EdgeDirection, Expr, LabelType, + PropertyType, Quantifier, SimpleType, UnOpKind, Var, + }; + use std::collections::HashMap; + + // ========================================== + // NODE PATTERN TESTS + // ========================================== + + #[test] + fn test_node_empty() { + let result = parse("()").unwrap(); + let expected = PathPattern::Node(Descriptor { + variable: None, + descriptor_type: DescriptorType { + label: LabelType::Star, + properties: PropertyType::open(), + }, + }); + assert_eq!(result, expected); + } + + #[test] + fn test_node_variable() { + let result = parse("(x)").unwrap(); + let expected = PathPattern::Node(Descriptor { + variable: Some(Var("x".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Star, + properties: PropertyType::open(), + }, + }); + assert_eq!(result, expected); + } + + #[test] + fn test_descriptor() { + let result = parse("(x:Person)").unwrap(); + let expected = PathPattern::Node(Descriptor { + variable: Some(Var("x".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Label("Person".to_string()), + properties: PropertyType::open(), + }, + }); + assert_eq!(result, expected); + } + + #[test] + fn test_descriptor_empty_record() { + let result = parse("(x:Person {})").unwrap(); + let expected = PathPattern::Node(Descriptor { + variable: Some(Var("x".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Label("Person".to_string()), + properties: PropertyType::open(), + }, + }); + assert_eq!(result, expected); + } + + #[test] + fn test_descriptor_record() { + let result = parse("(x :Person {a: int})").unwrap(); + let mut props = HashMap::new(); + props.insert("a".to_string(), SimpleType::Base(BaseType::Int)); + let expected = PathPattern::Node(Descriptor { + variable: Some(Var("x".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Label("Person".to_string()), + properties: PropertyType::Open(props), + }, + }); + assert_eq!(result, expected); + } + + #[test] + fn test_descriptor_record_multiple() { + let result = parse("(:Person {a: int, b: bool})").unwrap(); + let mut props = HashMap::new(); + props.insert("a".to_string(), SimpleType::Base(BaseType::Int)); + props.insert("b".to_string(), SimpleType::Base(BaseType::Bool)); + let expected = PathPattern::Node(Descriptor { + variable: None, + descriptor_type: DescriptorType { + label: LabelType::Label("Person".to_string()), + properties: PropertyType::Open(props), + }, + }); + assert_eq!(result, expected); + } + + #[test] + fn test_descriptor_no_label() { + let result = parse("(:{a: int, b: bool})").unwrap(); + let mut props = HashMap::new(); + props.insert("a".to_string(), SimpleType::Base(BaseType::Int)); + props.insert("b".to_string(), SimpleType::Base(BaseType::Bool)); + let expected = PathPattern::Node(Descriptor { + variable: None, + descriptor_type: DescriptorType { + label: LabelType::Star, + properties: PropertyType::Open(props), + }, + }); + assert_eq!(result, expected); + } + + #[test] + fn test_descriptor_record_closed() { + let result = parse("(x :Person {{a: int}})").unwrap(); + let mut props = HashMap::new(); + props.insert("a".to_string(), SimpleType::Base(BaseType::Int)); + let expected = PathPattern::Node(Descriptor { + variable: Some(Var("x".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Label("Person".to_string()), + properties: PropertyType::Closed(props), + }, + }); + assert_eq!(result, expected); + } + + #[test] + fn test_label_and() { + let result = parse("(:Person & Company)").unwrap(); + let expected = PathPattern::Node(Descriptor { + variable: None, + descriptor_type: DescriptorType { + label: LabelType::and( + LabelType::Label("Person".to_string()), + LabelType::Label("Company".to_string()), + ), + properties: PropertyType::open(), + }, + }); + assert_eq!(result, expected); + } + + // ========================================== + // EDGE PATTERN TESTS + // ========================================== + + #[test] + fn test_edge_right_empty() { + let result = parse("->").unwrap(); + let expected = PathPattern::Edge(EdgeDirection::Right, Descriptor::default()); + assert_eq!(result, expected); + } + + #[test] + fn test_edge_right_empty_alt() { + let result = parse("-[]->").unwrap(); + let expected = PathPattern::Edge(EdgeDirection::Right, Descriptor::default()); + assert_eq!(result, expected); + } + + #[test] + fn test_edge_right_with_descriptor() { + let result = parse("-[x:Person {a: int}]->").unwrap(); + let mut props = HashMap::new(); + props.insert("a".to_string(), SimpleType::Base(BaseType::Int)); + let expected = PathPattern::Edge( + EdgeDirection::Right, + Descriptor { + variable: Some(Var("x".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Label("Person".to_string()), + properties: PropertyType::Open(props), + }, + }, + ); + assert_eq!(result, expected); + } + + #[test] + fn test_edge_left_empty() { + let result = parse("<-").unwrap(); + let expected = PathPattern::Edge(EdgeDirection::Left, Descriptor::default()); + assert_eq!(result, expected); + } + + #[test] + fn test_edge_left_empty_alt() { + let result = parse("<-[]-").unwrap(); + let expected = PathPattern::Edge(EdgeDirection::Left, Descriptor::default()); + assert_eq!(result, expected); + } + + #[test] + fn test_edge_left_with_descriptor() { + let result = parse("<-[x:Person {a: int}]-").unwrap(); + let mut props = HashMap::new(); + props.insert("a".to_string(), SimpleType::Base(BaseType::Int)); + let expected = PathPattern::Edge( + EdgeDirection::Left, + Descriptor { + variable: Some(Var("x".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Label("Person".to_string()), + properties: PropertyType::Open(props), + }, + }, + ); + assert_eq!(result, expected); + } + + #[test] + fn test_edge_non_directional_empty() { + let result = parse("~").unwrap(); + let expected = PathPattern::Edge(EdgeDirection::None, Descriptor::default()); + assert_eq!(result, expected); + } + + #[test] + fn test_edge_non_directional_empty_alt() { + let result = parse("~[]~").unwrap(); + let expected = PathPattern::Edge(EdgeDirection::None, Descriptor::default()); + assert_eq!(result, expected); + } + + #[test] + fn test_edge_non_directional_with_descriptor() { + let result = parse("~[x:Person {a: int}]~").unwrap(); + let mut props = HashMap::new(); + props.insert("a".to_string(), SimpleType::Base(BaseType::Int)); + let expected = PathPattern::Edge( + EdgeDirection::None, + Descriptor { + variable: Some(Var("x".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Label("Person".to_string()), + properties: PropertyType::Open(props), + }, + }, + ); + assert_eq!(result, expected); + } + + // ========================================== + // CONCATENATION PATTERN TESTS + // ========================================== + + #[test] + fn test_concatenation() { + let result = parse("(x)~[y]~(z)").unwrap(); + let x = PathPattern::Node(Descriptor { + variable: Some(Var("x".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Star, + properties: PropertyType::open(), + }, + }); + let y = PathPattern::Edge( + EdgeDirection::None, + Descriptor { + variable: Some(Var("y".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Star, + properties: PropertyType::open(), + }, + }, + ); + let z = PathPattern::Node(Descriptor { + variable: Some(Var("z".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Star, + properties: PropertyType::open(), + }, + }); + let expected = PathPattern::concat(PathPattern::concat(x, y), z); + assert_eq!(result, expected); + } + + #[test] + fn test_union() { + let result = parse("() | ()").unwrap(); + let a = PathPattern::Node(Descriptor::default()); + let b = PathPattern::Node(Descriptor::default()); + let expected = PathPattern::union(a, b); + assert_eq!(result, expected); + } + + // ========================================== + // FILTER PATTERN TESTS + // ========================================== + + #[test] + fn test_filter_attribute_gt() { + let result = parse("(x where x.a>10)").unwrap(); + let expected = PathPattern::filter( + PathPattern::Node(Descriptor { + variable: Some(Var("x".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Star, + properties: PropertyType::open(), + }, + }), + Expr::binop( + BinOpKind::Gt, + Expr::attr_lookup(Var("x".to_string()), Var("a".to_string())), + Expr::Constant(Constant::Int(10)), + ), + ); + assert_eq!(result, expected); + } + + #[test] + fn test_filter_and() { + let result = parse("(x where 11>10 and (1 = 2 or 3>='1'))").unwrap(); + let expected = PathPattern::filter( + PathPattern::Node(Descriptor { + variable: Some(Var("x".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Star, + properties: PropertyType::open(), + }, + }), + Expr::binop( + BinOpKind::And, + Expr::binop( + BinOpKind::Gt, + Expr::Constant(Constant::Int(11)), + Expr::Constant(Constant::Int(10)), + ), + Expr::binop( + BinOpKind::Or, + Expr::binop( + BinOpKind::Eq, + Expr::Constant(Constant::Int(1)), + Expr::Constant(Constant::Int(2)), + ), + Expr::binop( + BinOpKind::Ge, + Expr::Constant(Constant::Int(3)), + Expr::Constant(Constant::String("1".to_string())), + ), + ), + ), + ); + assert_eq!(result, expected); + } + + #[test] + fn test_filter_on_edge() { + let result = parse("(x)-[y where y.a>10]->(z)").unwrap(); + let x = PathPattern::Node(Descriptor { + variable: Some(Var("x".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Star, + properties: PropertyType::open(), + }, + }); + let y = PathPattern::filter( + PathPattern::Edge( + EdgeDirection::Right, + Descriptor { + variable: Some(Var("y".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Star, + properties: PropertyType::open(), + }, + }, + ), + Expr::binop( + BinOpKind::Gt, + Expr::attr_lookup(Var("y".to_string()), Var("a".to_string())), + Expr::Constant(Constant::Int(10)), + ), + ); + let z = PathPattern::Node(Descriptor { + variable: Some(Var("z".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Star, + properties: PropertyType::open(), + }, + }); + let expected = PathPattern::concat(PathPattern::concat(x, y), z); + assert_eq!(result, expected); + } + + #[test] + fn test_prioritization() { + let result = parse("(x where 11 = 10 and 1 = 2 or 1=2)").unwrap(); + let expected = PathPattern::filter( + PathPattern::Node(Descriptor { + variable: Some(Var("x".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Star, + properties: PropertyType::open(), + }, + }), + Expr::binop( + BinOpKind::Or, + Expr::binop( + BinOpKind::And, + Expr::binop( + BinOpKind::Eq, + Expr::Constant(Constant::Int(11)), + Expr::Constant(Constant::Int(10)), + ), + Expr::binop( + BinOpKind::Eq, + Expr::Constant(Constant::Int(1)), + Expr::Constant(Constant::Int(2)), + ), + ), + Expr::binop( + BinOpKind::Eq, + Expr::Constant(Constant::Int(1)), + Expr::Constant(Constant::Int(2)), + ), + ), + ); + assert_eq!(result, expected); + } + + #[test] + fn test_simple_logical() { + let result = parse("(x where true and 1>2)").unwrap(); + let expected = PathPattern::filter( + PathPattern::Node(Descriptor { + variable: Some(Var("x".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Star, + properties: PropertyType::open(), + }, + }), + Expr::binop( + BinOpKind::And, + Expr::Constant(Constant::Bool(true)), + Expr::binop( + BinOpKind::Gt, + Expr::Constant(Constant::Int(1)), + Expr::Constant(Constant::Int(2)), + ), + ), + ); + assert_eq!(result, expected); + } + + #[test] + fn test_simple_arithmetic() { + let result = parse("(x where x.a>x.b>1)").unwrap(); + let expected = PathPattern::filter( + PathPattern::Node(Descriptor { + variable: Some(Var("x".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Star, + properties: PropertyType::open(), + }, + }), + Expr::binop( + BinOpKind::Gt, + Expr::binop( + BinOpKind::Gt, + Expr::attr_lookup(Var("x".to_string()), Var("a".to_string())), + Expr::attr_lookup(Var("x".to_string()), Var("b".to_string())), + ), + Expr::Constant(Constant::Int(1)), + ), + ); + assert_eq!(result, expected); + } + + #[test] + fn test_unop_1() { + let result = parse("(x WHERE not x.status)").unwrap(); + let expected = PathPattern::filter( + PathPattern::Node(Descriptor { + variable: Some(Var("x".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Star, + properties: PropertyType::open(), + }, + }), + Expr::unop( + UnOpKind::Not, + Expr::attr_lookup(Var("x".to_string()), Var("status".to_string())), + ), + ); + assert_eq!(result, expected); + } + + #[test] + fn test_unop_2() { + let result = parse("(x WHERE -x.status>0)").unwrap(); + let expected = PathPattern::filter( + PathPattern::Node(Descriptor { + variable: Some(Var("x".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Star, + properties: PropertyType::open(), + }, + }), + Expr::binop( + BinOpKind::Gt, + Expr::unop( + UnOpKind::Neg, + Expr::attr_lookup(Var("x".to_string()), Var("status".to_string())), + ), + Expr::Constant(Constant::Int(0)), + ), + ); + assert_eq!(result, expected); + } + + #[test] + fn test_unop_3() { + let result = parse("((x) WHERE -x.status>0)").unwrap(); + let expected = PathPattern::filter( + PathPattern::Node(Descriptor { + variable: Some(Var("x".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Star, + properties: PropertyType::open(), + }, + }), + Expr::binop( + BinOpKind::Gt, + Expr::unop( + UnOpKind::Neg, + Expr::attr_lookup(Var("x".to_string()), Var("status".to_string())), + ), + Expr::Constant(Constant::Int(0)), + ), + ); + assert_eq!(result, expected); + } + + // ========================================== + // REPETITION PATTERN TESTS + // ========================================== + + #[test] + fn test_repetition() { + let x_node = || { + PathPattern::Node(Descriptor { + variable: Some(Var("x".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Star, + properties: PropertyType::open(), + }, + }) + }; + + // (x)* → zero or more + assert_eq!( + parse("(x)*").unwrap(), + PathPattern::quantified(x_node(), Quantifier::Star) + ); + + // (x)+ → one or more + assert_eq!( + parse("(x)+").unwrap(), + PathPattern::quantified(x_node(), Quantifier::Plus) + ); + + // (x){1,2} → between 1 and 2 + assert_eq!( + parse("(x){1,2}").unwrap(), + PathPattern::quantified(x_node(), Quantifier::Range(Some(1), Some(2))) + ); + + // (x){2,} → 2 or more + assert_eq!( + parse("(x){2,}").unwrap(), + PathPattern::quantified(x_node(), Quantifier::Range(Some(2), None)) + ); + } + + #[test] + fn test_repetition_no_max() { + // (x){1,} → 1 or more (no upper bound) + let result = parse("(x){1,}").unwrap(); + let inner = PathPattern::Node(Descriptor { + variable: Some(Var("x".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Star, + properties: PropertyType::open(), + }, + }); + let expected = PathPattern::quantified(inner, Quantifier::Range(Some(1), None)); + assert_eq!(result, expected); + } + + #[test] + fn test_questioned_edge() { + // -[z]->? → optional edge + let result = parse("-[z]->?").unwrap(); + let inner = PathPattern::Edge( + EdgeDirection::Right, + Descriptor { + variable: Some(Var("z".to_string())), + descriptor_type: DescriptorType { + label: LabelType::Star, + properties: PropertyType::open(), + }, + }, + ); + let expected = PathPattern::questioned(inner); + assert_eq!(result, expected); + } +} From c021c45e564ef23a1519096e19f79df8a342e2df Mon Sep 17 00:00:00 2001 From: Felipe705x Date: Sun, 22 Feb 2026 16:49:40 -0300 Subject: [PATCH 2/2] refator: simplify main and lib --- src/ast/mod.rs | 13 - src/grammar/descriptor.lalrpop | 2 +- src/grammar/expr.lalrpop | 2 +- src/lib.rs | 648 +-------------------------------- src/main.rs | 49 +-- 5 files changed, 12 insertions(+), 702 deletions(-) delete mode 100644 src/ast/mod.rs diff --git a/src/ast/mod.rs b/src/ast/mod.rs deleted file mode 100644 index e6849ef..0000000 --- a/src/ast/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -mod descriptor; -mod expr; -mod label; -mod pattern; -mod types; -mod var; - -pub use descriptor::{Descriptor, DescriptorType, PropertyType}; -pub use expr::{BinOpKind, Constant, Expr, UnOpKind}; -pub use label::LabelType; -pub use pattern::{EdgeDirection, PathPattern, Quantifier}; -pub use types::{BaseType, SimpleType}; -pub use var::Var; diff --git a/src/grammar/descriptor.lalrpop b/src/grammar/descriptor.lalrpop index 5895665..6901700 100644 --- a/src/grammar/descriptor.lalrpop +++ b/src/grammar/descriptor.lalrpop @@ -1,4 +1,4 @@ -pub Descriptor: Descriptor = { +Descriptor: Descriptor = { ":" => Descriptor::new(var.into(), ty), ":" => Descriptor::with_type(ty), => Descriptor::with_variable(var.into()), diff --git a/src/grammar/expr.lalrpop b/src/grammar/expr.lalrpop index eef5a4c..cb23d1f 100644 --- a/src/grammar/expr.lalrpop +++ b/src/grammar/expr.lalrpop @@ -1,4 +1,4 @@ -pub Expr: Expr = { +Expr: Expr = { #[precedence(level="6")] #[assoc(side="left")] OR => Expr::binop(BinOpKind::Or, l, r), diff --git a/src/lib.rs b/src/lib.rs index e573ae4..e4e2319 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,648 +1,4 @@ -use lalrpop_util::lalrpop_mod; - pub mod ast; +pub mod parser; -lalrpop_mod!(pub grammar); - -pub use crate::grammar::{DescriptorParser, ExprParser, PathPatternParser}; - -#[cfg(test)] -mod tests { - use super::*; - use ast::{ - BaseType, BinOpKind, Constant, Expr, LabelType, PropertyType, SimpleType, UnOpKind, Var, - }; - use ast::{Descriptor, DescriptorType, EdgeDirection, PathPattern, Quantifier}; - use std::collections::HashMap; - - // ========================================== - // NODE PATTERN TESTS - // ========================================== - - #[test] - fn test_node_empty() { - let result = PathPatternParser::new().parse("()").unwrap(); - let expected = PathPattern::Node(Descriptor { - variable: None, - descriptor_type: DescriptorType { - label: LabelType::Star, - properties: PropertyType::open(), - }, - }); - assert_eq!(result, expected); - } - - #[test] - fn test_node_variable() { - let result = PathPatternParser::new().parse("(x)").unwrap(); - let expected = PathPattern::Node(Descriptor { - variable: Some(Var("x".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Star, - properties: PropertyType::open(), - }, - }); - assert_eq!(result, expected); - } - - #[test] - fn test_descriptor() { - let result = PathPatternParser::new().parse("(x:Person)").unwrap(); - let expected = PathPattern::Node(Descriptor { - variable: Some(Var("x".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Label("Person".to_string()), - properties: PropertyType::open(), - }, - }); - assert_eq!(result, expected); - } - - #[test] - fn test_descriptor_empty_record() { - let result = PathPatternParser::new().parse("(x:Person {})").unwrap(); - let expected = PathPattern::Node(Descriptor { - variable: Some(Var("x".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Label("Person".to_string()), - properties: PropertyType::open(), - }, - }); - assert_eq!(result, expected); - } - - #[test] - fn test_descriptor_record() { - let result = PathPatternParser::new() - .parse("(x :Person {a: int})") - .unwrap(); - let mut props = HashMap::new(); - props.insert("a".to_string(), SimpleType::Base(BaseType::Int)); - let expected = PathPattern::Node(Descriptor { - variable: Some(Var("x".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Label("Person".to_string()), - properties: PropertyType::Open(props), - }, - }); - assert_eq!(result, expected); - } - - #[test] - fn test_descriptor_record_multiple() { - let result = PathPatternParser::new() - .parse("(:Person {a: int, b: bool})") - .unwrap(); - let mut props = HashMap::new(); - props.insert("a".to_string(), SimpleType::Base(BaseType::Int)); - props.insert("b".to_string(), SimpleType::Base(BaseType::Bool)); - let expected = PathPattern::Node(Descriptor { - variable: None, - descriptor_type: DescriptorType { - label: LabelType::Label("Person".to_string()), - properties: PropertyType::Open(props), - }, - }); - assert_eq!(result, expected); - } - - #[test] - fn test_descriptor_no_label() { - let result = PathPatternParser::new() - .parse("(:{a: int, b: bool})") - .unwrap(); - let mut props = HashMap::new(); - props.insert("a".to_string(), SimpleType::Base(BaseType::Int)); - props.insert("b".to_string(), SimpleType::Base(BaseType::Bool)); - let expected = PathPattern::Node(Descriptor { - variable: None, - descriptor_type: DescriptorType { - label: LabelType::Star, - properties: PropertyType::Open(props), - }, - }); - assert_eq!(result, expected); - } - - #[test] - fn test_descriptor_record_closed() { - let result = PathPatternParser::new() - .parse("(x :Person {{a: int}})") - .unwrap(); - let mut props = HashMap::new(); - props.insert("a".to_string(), SimpleType::Base(BaseType::Int)); - let expected = PathPattern::Node(Descriptor { - variable: Some(Var("x".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Label("Person".to_string()), - properties: PropertyType::Closed(props), - }, - }); - assert_eq!(result, expected); - } - - #[test] - fn test_label_and() { - let result = PathPatternParser::new() - .parse("(:Person & Company)") - .unwrap(); - let expected = PathPattern::Node(Descriptor { - variable: None, - descriptor_type: DescriptorType { - label: LabelType::and( - LabelType::Label("Person".to_string()), - LabelType::Label("Company".to_string()), - ), - properties: PropertyType::open(), - }, - }); - assert_eq!(result, expected); - } - - // ========================================== - // EDGE PATTERN TESTS - // ========================================== - - #[test] - fn test_edge_right_empty() { - let result = PathPatternParser::new().parse("->").unwrap(); - let expected = PathPattern::Edge(EdgeDirection::Right, Descriptor::default()); - assert_eq!(result, expected); - } - - #[test] - fn test_edge_right_empty_alt() { - let result = PathPatternParser::new().parse("-[]->").unwrap(); - let expected = PathPattern::Edge(EdgeDirection::Right, Descriptor::default()); - assert_eq!(result, expected); - } - - #[test] - fn test_edge_right_with_descriptor() { - let result = PathPatternParser::new() - .parse("-[x:Person {a: int}]->") - .unwrap(); - let mut props = HashMap::new(); - props.insert("a".to_string(), SimpleType::Base(BaseType::Int)); - let expected = PathPattern::Edge( - EdgeDirection::Right, - Descriptor { - variable: Some(Var("x".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Label("Person".to_string()), - properties: PropertyType::Open(props), - }, - }, - ); - assert_eq!(result, expected); - } - - #[test] - fn test_edge_left_empty() { - let result = PathPatternParser::new().parse("<-").unwrap(); - let expected = PathPattern::Edge(EdgeDirection::Left, Descriptor::default()); - assert_eq!(result, expected); - } - - #[test] - fn test_edge_left_empty_alt() { - let result = PathPatternParser::new().parse("<-[]-").unwrap(); - let expected = PathPattern::Edge(EdgeDirection::Left, Descriptor::default()); - assert_eq!(result, expected); - } - - #[test] - fn test_edge_left_with_descriptor() { - let result = PathPatternParser::new() - .parse("<-[x:Person {a: int}]-") - .unwrap(); - let mut props = HashMap::new(); - props.insert("a".to_string(), SimpleType::Base(BaseType::Int)); - let expected = PathPattern::Edge( - EdgeDirection::Left, - Descriptor { - variable: Some(Var("x".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Label("Person".to_string()), - properties: PropertyType::Open(props), - }, - }, - ); - assert_eq!(result, expected); - } - - #[test] - fn test_edge_non_directional_empty() { - let result = PathPatternParser::new().parse("~").unwrap(); - let expected = PathPattern::Edge(EdgeDirection::None, Descriptor::default()); - assert_eq!(result, expected); - } - - #[test] - fn test_edge_non_directional_empty_alt() { - let result = PathPatternParser::new().parse("~[]~").unwrap(); - let expected = PathPattern::Edge(EdgeDirection::None, Descriptor::default()); - assert_eq!(result, expected); - } - - #[test] - fn test_edge_non_directional_with_descriptor() { - let result = PathPatternParser::new() - .parse("~[x:Person {a: int}]~") - .unwrap(); - let mut props = HashMap::new(); - props.insert("a".to_string(), SimpleType::Base(BaseType::Int)); - let expected = PathPattern::Edge( - EdgeDirection::None, - Descriptor { - variable: Some(Var("x".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Label("Person".to_string()), - properties: PropertyType::Open(props), - }, - }, - ); - assert_eq!(result, expected); - } - - // ========================================== - // CONCATENATION PATTERN TESTS - // ========================================== - - #[test] - fn test_concatenation() { - let result = PathPatternParser::new().parse("(x)~[y]~(z)").unwrap(); - let x = PathPattern::Node(Descriptor { - variable: Some(Var("x".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Star, - properties: PropertyType::open(), - }, - }); - let y = PathPattern::Edge( - EdgeDirection::None, - Descriptor { - variable: Some(Var("y".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Star, - properties: PropertyType::open(), - }, - }, - ); - let z = PathPattern::Node(Descriptor { - variable: Some(Var("z".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Star, - properties: PropertyType::open(), - }, - }); - let expected = PathPattern::concat(PathPattern::concat(x, y), z); - assert_eq!(result, expected); - } - - #[test] - fn test_union() { - let result = PathPatternParser::new().parse("() | ()").unwrap(); - let a = PathPattern::Node(Descriptor::default()); - let b = PathPattern::Node(Descriptor::default()); - let expected = PathPattern::union(a, b); - assert_eq!(result, expected); - } - - // ========================================== - // FILTER PATTERN TESTS - // ========================================== - - #[test] - fn test_filter_attribute_gt() { - let result = PathPatternParser::new().parse("(x where x.a>10)").unwrap(); - let expected = PathPattern::filter( - PathPattern::Node(Descriptor { - variable: Some(Var("x".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Star, - properties: PropertyType::open(), - }, - }), - Expr::binop( - BinOpKind::Gt, - Expr::attr_lookup(Var("x".to_string()), Var("a".to_string())), - Expr::Constant(Constant::Int(10)), - ), - ); - assert_eq!(result, expected); - } - - #[test] - fn test_filter_and() { - let result = PathPatternParser::new() - .parse("(x where 11>10 and (1 = 2 or 3>='1'))") - .unwrap(); - let expected = PathPattern::filter( - PathPattern::Node(Descriptor { - variable: Some(Var("x".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Star, - properties: PropertyType::open(), - }, - }), - Expr::binop( - BinOpKind::And, - Expr::binop( - BinOpKind::Gt, - Expr::Constant(Constant::Int(11)), - Expr::Constant(Constant::Int(10)), - ), - Expr::binop( - BinOpKind::Or, - Expr::binop( - BinOpKind::Eq, - Expr::Constant(Constant::Int(1)), - Expr::Constant(Constant::Int(2)), - ), - Expr::binop( - BinOpKind::Ge, - Expr::Constant(Constant::Int(3)), - Expr::Constant(Constant::String("1".to_string())), - ), - ), - ), - ); - assert_eq!(result, expected); - } - - #[test] - fn test_filter_on_edge() { - let result = PathPatternParser::new() - .parse("(x)-[y where y.a>10]->(z)") - .unwrap(); - let x = PathPattern::Node(Descriptor { - variable: Some(Var("x".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Star, - properties: PropertyType::open(), - }, - }); - let y = PathPattern::filter( - PathPattern::Edge( - EdgeDirection::Right, - Descriptor { - variable: Some(Var("y".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Star, - properties: PropertyType::open(), - }, - }, - ), - Expr::binop( - BinOpKind::Gt, - Expr::attr_lookup(Var("y".to_string()), Var("a".to_string())), - Expr::Constant(Constant::Int(10)), - ), - ); - let z = PathPattern::Node(Descriptor { - variable: Some(Var("z".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Star, - properties: PropertyType::open(), - }, - }); - let expected = PathPattern::concat(PathPattern::concat(x, y), z); - assert_eq!(result, expected); - } - - #[test] - fn test_prioritization() { - let result = PathPatternParser::new() - .parse("(x where 11 = 10 and 1 = 2 or 1=2)") - .unwrap(); - let expected = PathPattern::filter( - PathPattern::Node(Descriptor { - variable: Some(Var("x".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Star, - properties: PropertyType::open(), - }, - }), - Expr::binop( - BinOpKind::Or, - Expr::binop( - BinOpKind::And, - Expr::binop( - BinOpKind::Eq, - Expr::Constant(Constant::Int(11)), - Expr::Constant(Constant::Int(10)), - ), - Expr::binop( - BinOpKind::Eq, - Expr::Constant(Constant::Int(1)), - Expr::Constant(Constant::Int(2)), - ), - ), - Expr::binop( - BinOpKind::Eq, - Expr::Constant(Constant::Int(1)), - Expr::Constant(Constant::Int(2)), - ), - ), - ); - assert_eq!(result, expected); - } - - #[test] - fn test_simple_logical() { - let result = PathPatternParser::new() - .parse("(x where true and 1>2)") - .unwrap(); - let expected = PathPattern::filter( - PathPattern::Node(Descriptor { - variable: Some(Var("x".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Star, - properties: PropertyType::open(), - }, - }), - Expr::binop( - BinOpKind::And, - Expr::Constant(Constant::Bool(true)), - Expr::binop( - BinOpKind::Gt, - Expr::Constant(Constant::Int(1)), - Expr::Constant(Constant::Int(2)), - ), - ), - ); - assert_eq!(result, expected); - } - - #[test] - fn test_simple_arithmetic() { - let result = PathPatternParser::new() - .parse("(x where x.a>x.b>1)") - .unwrap(); - let expected = PathPattern::filter( - PathPattern::Node(Descriptor { - variable: Some(Var("x".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Star, - properties: PropertyType::open(), - }, - }), - Expr::binop( - BinOpKind::Gt, - Expr::binop( - BinOpKind::Gt, - Expr::attr_lookup(Var("x".to_string()), Var("a".to_string())), - Expr::attr_lookup(Var("x".to_string()), Var("b".to_string())), - ), - Expr::Constant(Constant::Int(1)), - ), - ); - assert_eq!(result, expected); - } - - #[test] - fn test_unop_1() { - let result = PathPatternParser::new() - .parse("(x WHERE not x.status)") - .unwrap(); - let expected = PathPattern::filter( - PathPattern::Node(Descriptor { - variable: Some(Var("x".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Star, - properties: PropertyType::open(), - }, - }), - Expr::unop( - UnOpKind::Not, - Expr::attr_lookup(Var("x".to_string()), Var("status".to_string())), - ), - ); - assert_eq!(result, expected); - } - - #[test] - fn test_unop_2() { - let result = PathPatternParser::new() - .parse("(x WHERE -x.status>0)") - .unwrap(); - let expected = PathPattern::filter( - PathPattern::Node(Descriptor { - variable: Some(Var("x".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Star, - properties: PropertyType::open(), - }, - }), - Expr::binop( - BinOpKind::Gt, - Expr::unop( - UnOpKind::Neg, - Expr::attr_lookup(Var("x".to_string()), Var("status".to_string())), - ), - Expr::Constant(Constant::Int(0)), - ), - ); - assert_eq!(result, expected); - } - - #[test] - fn test_unop_3() { - let result = PathPatternParser::new() - .parse("((x) WHERE -x.status>0)") - .unwrap(); - let expected = PathPattern::filter( - PathPattern::Node(Descriptor { - variable: Some(Var("x".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Star, - properties: PropertyType::open(), - }, - }), - Expr::binop( - BinOpKind::Gt, - Expr::unop( - UnOpKind::Neg, - Expr::attr_lookup(Var("x".to_string()), Var("status".to_string())), - ), - Expr::Constant(Constant::Int(0)), - ), - ); - assert_eq!(result, expected); - } - - // ========================================== - // REPETITION PATTERN TESTS - // ========================================== - - #[test] - fn test_repetition() { - let x_node = || { - PathPattern::Node(Descriptor { - variable: Some(Var("x".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Star, - properties: PropertyType::open(), - }, - }) - }; - - // (x)* → zero or more - assert_eq!( - PathPatternParser::new().parse("(x)*").unwrap(), - PathPattern::quantified(x_node(), Quantifier::Star) - ); - - // (x)+ → one or more - assert_eq!( - PathPatternParser::new().parse("(x)+").unwrap(), - PathPattern::quantified(x_node(), Quantifier::Plus) - ); - - // (x){1,2} → between 1 and 2 - assert_eq!( - PathPatternParser::new().parse("(x){1,2}").unwrap(), - PathPattern::quantified(x_node(), Quantifier::Range(Some(1), Some(2))) - ); - - // (x){2,} → 2 or more - assert_eq!( - PathPatternParser::new().parse("(x){2,}").unwrap(), - PathPattern::quantified(x_node(), Quantifier::Range(Some(2), None)) - ); - } - - #[test] - fn test_repetition_no_max() { - // (x){1,} → 1 or more (no upper bound) - let result = PathPatternParser::new().parse("(x){1,}").unwrap(); - let inner = PathPattern::Node(Descriptor { - variable: Some(Var("x".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Star, - properties: PropertyType::open(), - }, - }); - let expected = PathPattern::quantified(inner, Quantifier::Range(Some(1), None)); - assert_eq!(result, expected); - } - - #[test] - fn test_questioned_edge() { - // -[z]->? → optional edge - let result = PathPatternParser::new().parse("-[z]->?").unwrap(); - let inner = PathPattern::Edge( - EdgeDirection::Right, - Descriptor { - variable: Some(Var("z".to_string())), - descriptor_type: DescriptorType { - label: LabelType::Star, - properties: PropertyType::open(), - }, - }, - ); - let expected = PathPattern::questioned(inner); - assert_eq!(result, expected); - } -} +pub use parser::parse; diff --git a/src/main.rs b/src/main.rs index 1ecf2b8..f4b5020 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,10 @@ -use fppc::*; +use fppc::parse; use std::io::{self, Write}; fn main() { println!("=== FPPC Parser Interactive Console ==="); - println!("Commands:"); - println!(" expr - Parse as Expr"); - println!(" descriptor - Parse as Descriptor"); - println!(" path - Parse as PathPattern"); - println!(" pretty - Toggle pretty printing"); - println!(" quit - Exit"); + println!("Enter a path pattern to parse, or 'quit' to exit."); + println!("Toggle pretty printing with 'pretty'."); println!(); let mut pretty = false; @@ -38,44 +34,15 @@ fn main() { continue; } - let parts: Vec<&str> = input.splitn(2, ' ').collect(); - if parts.len() < 2 { - eprintln!("Error: Please provide a command and input. Example: path (p: Person)"); - continue; - } - - let command = parts[0]; - let parse_input = parts[1]; - - macro_rules! print_result { - ($result:expr) => { + match parse(input) { + Ok(result) => { if pretty { - println!("✓ Valid: {:#?}", $result) + println!("✓ {:#?}", result) } else { - println!("✓ Valid: {:?}", $result) + println!("✓ {:?}", result) } - }; - } - - match command { - "expr" => match ExprParser::new().parse(parse_input) { - Ok(result) => print_result!(result), - Err(e) => eprintln!("✗ Parse error: {}", e), - }, - "descriptor" => match DescriptorParser::new().parse(parse_input) { - Ok(result) => print_result!(result), - Err(e) => eprintln!("✗ Parse error: {}", e), - }, - "path" => match PathPatternParser::new().parse(parse_input) { - Ok(result) => print_result!(result), - Err(e) => eprintln!("✗ Parse error: {}", e), - }, - _ => { - eprintln!( - "Unknown command: {}. Use: expr, descriptor, or path", - command - ); } + Err(e) => eprintln!("✗ Parse error: {}", e), } } }