diff --git a/src/ast/expr.rs b/src/ast/expr.rs index b0ddfb2..681eb98 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -109,11 +109,8 @@ impl fmt::Debug for Unop { /// Represents a constant expression (string, int, or boolean). #[derive(PartialEq, Clone)] pub enum Constant { - /// A constant string value (SConstant in Python) String(String), - /// A constant integer (Z) value (ZConstant in Python) Int(i64), - /// A constant boolean value Bool(bool), } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 2b6de5f..89ad659 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -8,6 +8,6 @@ mod var; pub use descriptor::{Descriptor, DescriptorType, PropertyType}; pub use expr::{AttributeLookup, BinOpKind, Binop, Constant, Expr, UnOpKind, Unop}; pub use label::LabelType; -pub use pattern::{EdgeDirection, PathPattern}; +pub use pattern::{EdgeDirection, PathPattern, Quantifier}; pub use types::{BaseType, SimpleType}; pub use var::Var; diff --git a/src/ast/pattern.rs b/src/ast/pattern.rs index 0ae4508..a3cf866 100644 --- a/src/ast/pattern.rs +++ b/src/ast/pattern.rs @@ -2,6 +2,19 @@ use super::descriptor::Descriptor; use super::expr::Expr; use std::fmt; +/// Quantifiers for path pattern repetition +#[derive(PartialEq, Clone, Copy, Debug)] +pub enum Quantifier { + /// `*` - zero or more (equivalent to {0,}) + Star, + /// `+` - one or more (equivalent to {1,}) + Plus, + /// `{n}` - exactly n times + Fixed(u64), + /// `{m,n}` - between m and n times (both bounds optional) + Range(Option, Option), +} + #[derive(PartialEq, Clone)] pub enum PathPattern { Node(Descriptor), @@ -9,6 +22,10 @@ pub enum PathPattern { Edge(EdgeDirection, Descriptor), Concat(Box, Box), Union(Box, Box), + /// `p?` - optional pattern (zero or one) + Questioned(Box), + /// `p*`, `p+`, `p{n}`, `p{m,n}` - quantified pattern + Quantified(Box, Quantifier), } impl PathPattern { @@ -39,6 +56,14 @@ impl PathPattern { pub fn union(left: PathPattern, right: PathPattern) -> Self { PathPattern::Union(Box::new(left), Box::new(right)) } + + pub fn questioned(pattern: PathPattern) -> Self { + PathPattern::Questioned(Box::new(pattern)) + } + + pub fn quantified(pattern: PathPattern, quantifier: Quantifier) -> Self { + PathPattern::Quantified(Box::new(pattern), quantifier) + } } impl From for PathPattern { @@ -55,6 +80,8 @@ impl fmt::Debug for PathPattern { PathPattern::Edge(dir, desc) => write!(f, "Edge({:?}, {:?})", dir, desc), PathPattern::Concat(l, r) => write!(f, "Concat({:?}, {:?})", l, r), PathPattern::Union(l, r) => write!(f, "Union({:?}, {:?})", l, r), + PathPattern::Questioned(p) => write!(f, "Questioned({:?})", p), + PathPattern::Quantified(p, q) => write!(f, "Quantified({:?}, {:?})", p, q), } } } diff --git a/src/grammar/expr.lalrpop b/src/grammar/expr.lalrpop index 96873ef..eef5a4c 100644 --- a/src/grammar/expr.lalrpop +++ b/src/grammar/expr.lalrpop @@ -55,8 +55,6 @@ Boolean: bool = { FALSE => false, }; -Number: i64 = r"\d+" => <>.parse().unwrap(); - AND: () = { "and", "AND" }; OR: () = { "or", "OR" }; NOT: () = { "not", "NOT" }; diff --git a/src/grammar/mod.lalrpop b/src/grammar/mod.lalrpop index 5f9151e..ce4183f 100644 --- a/src/grammar/mod.lalrpop +++ b/src/grammar/mod.lalrpop @@ -1,6 +1,6 @@ use crate::ast::{ LabelType, SimpleType, BaseType, DescriptorType, - Descriptor, PropertyType, PathPattern, EdgeDirection, + Descriptor, PropertyType, PathPattern, EdgeDirection, Quantifier, Expr, Constant, BinOpKind, UnOpKind, }; use std::collections::HashMap; @@ -8,3 +8,17 @@ use std::collections::HashMap; grammar; Name: String = r"[a-zA-Z_][a-zA-Z0-9_]*" => <>.to_string(); + +Number: i64 = r"\d+" =>? { + <>.parse::() + .map_err(|_| lalrpop_util::ParseError::User { + error: "number literal exceeds maximum value (i64)" + }) +}; + +UnsignedNumber: u64 = r"\d+" =>? { + <>.parse::() + .map_err(|_| lalrpop_util::ParseError::User { + error: "number literal exceeds maximum value (u64)" + }) +}; diff --git a/src/grammar/pattern.lalrpop b/src/grammar/pattern.lalrpop index 799e2fe..b566944 100644 --- a/src/grammar/pattern.lalrpop +++ b/src/grammar/pattern.lalrpop @@ -3,10 +3,16 @@ pub PathPattern: PathPattern = { "|" => PathPattern::union(l, r), #[precedence(level="1")] #[assoc(side="left")] - => PathPattern::concat(l, r), + => PathPattern::concat(l, r), #[precedence(level="0")] + , +}; + +PathFactor: PathPattern = { , + => PathPattern::quantified(p, q), + "?" => PathPattern::questioned(p), }; PathPrimary: PathPattern = { @@ -44,3 +50,10 @@ ElementPatternFiller: (Descriptor, Option) = { }; WHERE: () = { "where", "WHERE" }; + +Quantifier: Quantifier = { + "*" => Quantifier::Star, + "+" => Quantifier::Plus, + "{" "}" => Quantifier::Fixed(n), + "{" "," "}" => Quantifier::Range(lo, hi), +}; diff --git a/src/lib.rs b/src/lib.rs index 85ffebe..fa1d170 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,8 @@ pub use crate::grammar::{ mod tests { use super::*; use ast::{ - AttributeLookup, Binop, Descriptor, DescriptorType, EdgeDirection, PathPattern, Unop, + AttributeLookup, Binop, Descriptor, DescriptorType, EdgeDirection, PathPattern, Quantifier, + Unop, }; use ast::{ BaseType, BinOpKind, Constant, Expr, LabelType, PropertyType, SimpleType, UnOpKind, Var, @@ -602,8 +603,76 @@ mod tests { } // ========================================== - // UNIMPLEMENTED FEATURES (skipped) + // REPETITION PATTERN TESTS // ========================================== - // - Repetition patterns (*, +, {n,m}) - // - Questioned patterns (?) + + #[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); + } }