use std::fmt;
#[derive(Debug, Clone)]
pub struct Query {
pub expressions: Vec<PipedExpr>,
}
impl Query {
pub fn new(expressions: Vec<PipedExpr>) -> Self {
Self { expressions }
}
}
#[derive(Debug, Clone)]
pub struct PipedExpr {
pub stages: Vec<Expr>,
}
impl PipedExpr {
pub fn new(stages: Vec<Expr>) -> Self {
Self { stages }
}
pub fn single(expr: Expr) -> Self {
Self { stages: vec![expr] }
}
}
#[derive(Debug, Clone)]
pub enum Expr {
Identity,
Element {
kind: ElementKind,
filters: Vec<Filter>,
index: Option<IndexOp>,
span: Span,
},
Property { name: String, span: Span },
Function {
name: String,
args: Vec<Expr>,
span: Span,
},
Object {
pairs: Vec<(String, Expr)>,
span: Span,
},
Array { elements: Vec<Expr>, span: Span },
Conditional {
condition: Box<Expr>,
then_branch: Box<Expr>,
else_branch: Option<Box<Expr>>,
span: Span,
},
Hierarchy {
parent: Box<Expr>,
child: Box<Expr>,
direct: bool,
span: Span,
},
Literal { value: Literal, span: Span },
Binary {
op: BinaryOp,
left: Box<Expr>,
right: Box<Expr>,
span: Span,
},
Unary {
op: UnaryOp,
expr: Box<Expr>,
span: Span,
},
Group { expr: Box<Expr>, span: Span },
}
impl Expr {
pub fn span(&self) -> Span {
match self {
Expr::Identity => Span::new(0, 1),
Expr::Element { span, .. } => *span,
Expr::Property { span, .. } => *span,
Expr::Function { span, .. } => *span,
Expr::Object { span, .. } => *span,
Expr::Array { span, .. } => *span,
Expr::Conditional { span, .. } => *span,
Expr::Hierarchy { span, .. } => *span,
Expr::Literal { span, .. } => *span,
Expr::Binary { span, .. } => *span,
Expr::Unary { span, .. } => *span,
Expr::Group { span, .. } => *span,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ElementKind {
Heading(Option<u8>),
Code,
Link,
Image,
Table,
List,
Blockquote,
Paragraph,
FrontMatter,
}
impl ElementKind {
pub fn from_str(s: &str) -> Option<Self> {
match s {
"h" | "heading" | "headings" | "header" | "headers" => Some(ElementKind::Heading(None)),
"h1" => Some(ElementKind::Heading(Some(1))),
"h2" => Some(ElementKind::Heading(Some(2))),
"h3" => Some(ElementKind::Heading(Some(3))),
"h4" => Some(ElementKind::Heading(Some(4))),
"h5" => Some(ElementKind::Heading(Some(5))),
"h6" => Some(ElementKind::Heading(Some(6))),
"code" | "codeblock" | "codeblocks" | "pre" => Some(ElementKind::Code),
"link" | "links" | "a" | "anchor" => Some(ElementKind::Link),
"img" | "image" | "images" => Some(ElementKind::Image),
"table" | "tables" => Some(ElementKind::Table),
"list" | "lists" | "ul" | "ol" => Some(ElementKind::List),
"blockquote" | "blockquotes" | "quote" | "quotes" | "bq" => {
Some(ElementKind::Blockquote)
}
"para" | "paragraph" | "paragraphs" | "p" => Some(ElementKind::Paragraph),
"frontmatter" | "fm" | "meta" | "yaml" => Some(ElementKind::FrontMatter),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
ElementKind::Heading(None) => "h",
ElementKind::Heading(Some(1)) => "h1",
ElementKind::Heading(Some(2)) => "h2",
ElementKind::Heading(Some(3)) => "h3",
ElementKind::Heading(Some(4)) => "h4",
ElementKind::Heading(Some(5)) => "h5",
ElementKind::Heading(Some(6)) => "h6",
ElementKind::Heading(Some(_)) => "h",
ElementKind::Code => "code",
ElementKind::Link => "link",
ElementKind::Image => "img",
ElementKind::Table => "table",
ElementKind::List => "list",
ElementKind::Blockquote => "blockquote",
ElementKind::Paragraph => "para",
ElementKind::FrontMatter => "frontmatter",
}
}
}
impl fmt::Display for ElementKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone)]
pub enum Filter {
Text {
pattern: String,
exact: bool,
span: Span,
},
Regex { pattern: String, span: Span },
Type { type_name: String, span: Span },
}
#[derive(Debug, Clone)]
pub enum IndexOp {
Single(i64),
Slice {
start: Option<i64>,
end: Option<i64>,
},
Iterate,
}
#[derive(Debug, Clone)]
pub enum Literal {
String(String),
Number(f64),
Bool(bool),
Null,
}
impl fmt::Display for Literal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Literal::String(s) => write!(f, "\"{}\"", s),
Literal::Number(n) => write!(f, "{}", n),
Literal::Bool(b) => write!(f, "{}", b),
Literal::Null => write!(f, "null"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinaryOp {
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
And,
Or,
Add,
Sub,
Mul,
Div,
Mod,
Concat,
Alt, }
impl BinaryOp {
pub fn as_str(&self) -> &'static str {
match self {
BinaryOp::Eq => "==",
BinaryOp::Ne => "!=",
BinaryOp::Lt => "<",
BinaryOp::Le => "<=",
BinaryOp::Gt => ">",
BinaryOp::Ge => ">=",
BinaryOp::And => "and",
BinaryOp::Or => "or",
BinaryOp::Add => "+",
BinaryOp::Sub => "-",
BinaryOp::Mul => "*",
BinaryOp::Div => "/",
BinaryOp::Mod => "%",
BinaryOp::Concat => "+",
BinaryOp::Alt => "//",
}
}
pub fn precedence(&self) -> u8 {
match self {
BinaryOp::Or => 1,
BinaryOp::And => 2,
BinaryOp::Eq | BinaryOp::Ne => 3,
BinaryOp::Lt | BinaryOp::Le | BinaryOp::Gt | BinaryOp::Ge => 4,
BinaryOp::Alt => 5,
BinaryOp::Add | BinaryOp::Sub | BinaryOp::Concat => 6,
BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod => 7,
}
}
}
impl fmt::Display for BinaryOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnaryOp {
Not,
Neg,
}
impl UnaryOp {
pub fn as_str(&self) -> &'static str {
match self {
UnaryOp::Not => "not",
UnaryOp::Neg => "-",
}
}
}
impl fmt::Display for UnaryOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl Span {
pub fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
pub fn merge(self, other: Span) -> Span {
Span {
start: self.start.min(other.start),
end: self.end.max(other.end),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_element_kind_from_str() {
assert_eq!(
ElementKind::from_str("h2"),
Some(ElementKind::Heading(Some(2)))
);
assert_eq!(ElementKind::from_str("code"), Some(ElementKind::Code));
assert_eq!(ElementKind::from_str("link"), Some(ElementKind::Link));
assert_eq!(ElementKind::from_str("unknown"), None);
}
#[test]
fn test_binary_op_precedence() {
assert!(BinaryOp::Mul.precedence() > BinaryOp::Add.precedence());
assert!(BinaryOp::And.precedence() > BinaryOp::Or.precedence());
assert!(BinaryOp::Eq.precedence() > BinaryOp::And.precedence());
}
}