Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions src/ast/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}

Expand Down
2 changes: 1 addition & 1 deletion src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
27 changes: 27 additions & 0 deletions src/ast/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,30 @@ 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<u64>, Option<u64>),
}

#[derive(PartialEq, Clone)]
pub enum PathPattern {
Node(Descriptor),
Filter(Box<PathPattern>, Expr),
Edge(EdgeDirection, Descriptor),
Concat(Box<PathPattern>, Box<PathPattern>),
Union(Box<PathPattern>, Box<PathPattern>),
/// `p?` - optional pattern (zero or one)
Questioned(Box<PathPattern>),
/// `p*`, `p+`, `p{n}`, `p{m,n}` - quantified pattern
Quantified(Box<PathPattern>, Quantifier),
}

impl PathPattern {
Expand Down Expand Up @@ -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<Descriptor> for PathPattern {
Expand All @@ -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),
}
}
}
Expand Down
2 changes: 0 additions & 2 deletions src/grammar/expr.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ Boolean: bool = {
FALSE => false,
};

Number: i64 = r"\d+" => <>.parse().unwrap();

AND: () = { "and", "AND" };
OR: () = { "or", "OR" };
NOT: () = { "not", "NOT" };
Expand Down
16 changes: 15 additions & 1 deletion src/grammar/mod.lalrpop
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
use crate::ast::{
LabelType, SimpleType, BaseType, DescriptorType,
Descriptor, PropertyType, PathPattern, EdgeDirection,
Descriptor, PropertyType, PathPattern, EdgeDirection, Quantifier,
Expr, Constant, BinOpKind, UnOpKind,
};
use std::collections::HashMap;

grammar;

Name: String = r"[a-zA-Z_][a-zA-Z0-9_]*" => <>.to_string();

Number: i64 = r"\d+" =>? {
<>.parse::<i64>()
.map_err(|_| lalrpop_util::ParseError::User {
error: "number literal exceeds maximum value (i64)"
})
};

UnsignedNumber: u64 = r"\d+" =>? {
<>.parse::<u64>()
.map_err(|_| lalrpop_util::ParseError::User {
error: "number literal exceeds maximum value (u64)"
})
};
15 changes: 14 additions & 1 deletion src/grammar/pattern.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ pub PathPattern: PathPattern = {
<l:PathPattern> "|" <r:PathPattern> => PathPattern::union(l, r),

#[precedence(level="1")] #[assoc(side="left")]
<l:PathPattern> <r:PathPrimary> => PathPattern::concat(l, r),
<l:PathPattern> <r:PathFactor> => PathPattern::concat(l, r),

#[precedence(level="0")]
<PathFactor>,
};

PathFactor: PathPattern = {
<PathPrimary>,
<p:PathPrimary> <q:Quantifier> => PathPattern::quantified(p, q),
<p:PathPrimary> "?" => PathPattern::questioned(p),
};

PathPrimary: PathPattern = {
Expand Down Expand Up @@ -44,3 +50,10 @@ ElementPatternFiller: (Descriptor, Option<Expr>) = {
};

WHERE: () = { "where", "WHERE" };

Quantifier: Quantifier = {
"*" => Quantifier::Star,
"+" => Quantifier::Plus,
"{" <n:UnsignedNumber> "}" => Quantifier::Fixed(n),
"{" <lo:UnsignedNumber?> "," <hi:UnsignedNumber?> "}" => Quantifier::Range(lo, hi),
};
77 changes: 73 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
}
Loading