Skip to content

Commit

Permalink
add recursion on urls
Browse files Browse the repository at this point in the history
- fix grammar to use inline expansion to specify url
- recurse on urls
- add interpreter struct
  • Loading branch information
suaviloquence committed Jul 13, 2024
1 parent 99c1e49 commit 07bbf0a
Show file tree
Hide file tree
Showing 10 changed files with 387 additions and 200 deletions.
51 changes: 51 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ tokio = { version = "1.38.0", features = ["full"] }
filter-proc-macro = { path = "./filter-proc-macro" }
serde_json = "1.0"
serde = { version = "1.0", features = ["derive", "rc"] }
futures = "0.3"

[workspace]
members = [".", "filter-proc-macro"]
5 changes: 3 additions & 2 deletions grammar.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ rvalue -> element

element -> maybe_url selector_list selector_ops `{` statement_list `}`

maybe_url -> variable
| STRING
inline -> `<` leaf filter_list `>`

maybe_url -> inline
| ""

selector_list -> selector selector2
Expand Down
2 changes: 2 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[toolchain]
channel = "beta"
9 changes: 4 additions & 5 deletions src/frontend/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,9 @@ pub enum RValue<'a> {
}

#[derive(Debug, Clone)]
pub enum Url<'a> {
Parent,
String(Cow<'a, str>),
Var(&'a str),
pub struct Inline<'a> {
pub value: Leaf<'a>,
pub filters: Option<AstRef<'a, FilterList<'a>>>,
}

#[derive(Debug, Clone)]
Expand All @@ -217,7 +216,7 @@ pub struct Statement<'a> {

#[derive(Debug, Clone)]
pub struct Element<'a> {
pub url: Url<'a>,
pub url: Option<Inline<'a>>,
pub selector_head: Selector<'a>,
pub selectors: Option<AstRef<'a, SelectorList<'a>>>,
pub ops: SelectorOpts,
Expand Down
41 changes: 25 additions & 16 deletions src/frontend/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use std::{borrow::Cow, ops::Not};
use super::{
arena::Arena,
ast::{
ArgList, Ast, AstRef, Element, FilterList, Leaf, RValue, Selector, SelectorCombinator,
SelectorList, SelectorOpts, Statement, StatementList, Url,
ArgList, Ast, AstRef, Element, FilterList, Inline, Leaf, RValue, Selector,
SelectorCombinator, SelectorList, SelectorOpts, Statement, StatementList,
},
scanner::{Lexeme, Scanner, Token},
};
Expand Down Expand Up @@ -88,7 +88,7 @@ impl<'a> Parser<'a> {
let lx = self.scanner.peek_non_whitespace();

match lx.token {
Token::Id => self.parse_element().map(RValue::Element),
Token::Id | Token::Less => self.parse_element().map(RValue::Element),
_ => self.parse_leaf().map(RValue::Leaf),
}
}
Expand Down Expand Up @@ -129,7 +129,7 @@ impl<'a> Parser<'a> {
}

fn parse_element(&mut self) -> Result<'a, Element<'a>> {
let url = self.parse_url()?;
let url = self.parse_maybe_url()?;
let selector_head = self.parse_selector()?;
let selectors = self.parse_selector_list()?;
let lx = self.scanner.peek_non_whitespace();
Expand Down Expand Up @@ -161,19 +161,28 @@ impl<'a> Parser<'a> {
})
}

fn parse_url(&mut self) -> Result<'a, Url<'a>> {
fn parse_maybe_url(&mut self) -> Result<'a, Option<Inline<'a>>> {
let lx = self.scanner.peek_non_whitespace();
match lx.token {
Token::Dollar => {
self.scanner.eat_token();
let id = self.try_eat(Token::Id)?.value;
Ok(Url::Var(id))
}
Token::String => {
self.scanner.eat_token();
Ok(Url::String(parse_string_literal(lx.value)))
}
_ => Ok(Url::Parent),
if lx.token == Token::Less {
self.parse_inline().map(Some)
} else {
Ok(None)
}
}

fn parse_inline(&mut self) -> Result<'a, Inline<'a>> {
let lx = self.scanner.peek_non_whitespace();
if lx.token == Token::Less {
self.scanner.eat_token();
let value = self.parse_leaf()?;
let filters = self.parse_filter_list()?;
self.try_eat(Token::Greater)?;
Ok(Inline { value, filters })
} else {
Err(ParseError::UnexpectedToken {
expected: vec![Token::Less],
got: lx,
})
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/frontend/scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub enum Token {
Star,
/// the selector combinator `+` to indicate the next sibling
Plus,
/// the selector combinator `>` to indicate a direct child
/// the selector combinator `>` to indicate a direct child or the end of an inline expansion
Greater,
/// the selector combinator `~` to indicate a subsequent sibling
Tilde,
Expand Down Expand Up @@ -44,6 +44,8 @@ pub enum Token {
Colon,
/// a semicolon `;` to indicate the end of a statement
Semi,
/// a less than sign `<` to indicate the start of an inline expansion
Less,
/// special token to indicate the end of the file
Eof,
/// special token to indicate unknown token
Expand Down Expand Up @@ -116,6 +118,7 @@ mod statics {
Comma <- ","
Colon <- ":"
Semi <- ";"
Less <- "<"
};
}
}
Expand Down
114 changes: 114 additions & 0 deletions src/interpreter/execution_mode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use std::{future::Future, iter, option, vec};

use crate::frontend::ast::SelectorOpts;

use super::DataValue;
use anyhow::Context;
use ExecutionMode::{Collection, One, Optional};

/// Whether we are matching a list, singular item, or optional item
/// as specified by the user
#[derive(Debug, Clone)]
pub enum ExecutionMode<T> {
One(T),
Optional(Option<T>),
Collection(Vec<T>),
}

#[derive(Debug)]
pub enum IntoIter<T> {
One(iter::Once<T>),
Optional(option::IntoIter<T>),
Collection(vec::IntoIter<T>),
}

impl<T> Iterator for IntoIter<T> {
type Item = T;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::One(x) => x.next(),
Self::Optional(x) => x.next(),
Self::Collection(x) => x.next(),
}
}
}

impl<T> IntoIterator for ExecutionMode<T> {
type Item = T;

type IntoIter = IntoIter<T>;

#[inline]
fn into_iter(self) -> Self::IntoIter {
match self {
One(x) => IntoIter::One(iter::once(x)),
Optional(x) => IntoIter::Optional(x.into_iter()),
Collection(x) => IntoIter::Collection(x.into_iter()),
}
}
}

impl<T> ExecutionMode<T> {
pub fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> ExecutionMode<U> {
match self {
One(x) => One(f(x)),
Optional(Some(x)) => Optional(Some(f(x))),
Optional(None) => Optional(None),
Collection(l) => Collection(l.into_iter().map(f).collect()),
}
}

#[inline]
pub async fn async_map<U, Fut: Future<Output = U>, Fn: FnMut(T) -> Fut>(
self,
f: Fn,
) -> ExecutionMode<U> {
self.map(f).transpose_fut().await
}

pub fn hinted_from_iter<I: Iterator<Item = T>>(
ops: SelectorOpts,
mut iter: I,
) -> anyhow::Result<Self> {
Ok(match ops {
// TODO: take the first, or fail if there are > 1?
SelectorOpts::One => One(iter.next().context("Expected exactly one value")?),
SelectorOpts::Optional => Optional(iter.next()),
SelectorOpts::Collection => Collection(iter.collect()),
})
}
}

impl ExecutionMode<DataValue> {
pub fn into_data_value(self) -> DataValue {
match self {
One(x) | Optional(Some(x)) => x,
Optional(None) => DataValue::Null,
Collection(l) => DataValue::List(l),
}
}
}

impl<T, E> ExecutionMode<Result<T, E>> {
pub fn transpose_res(self) -> Result<ExecutionMode<T>, E> {
Ok(match self {
One(x) => One(x?),
Optional(Some(x)) => Optional(Some(x?)),
Optional(None) => Optional(None),
Collection(l) => Collection(l.into_iter().collect::<Result<_, E>>()?),
})
}
}

impl<T, F: Future<Output = T>> ExecutionMode<F> {
pub async fn transpose_fut(self) -> ExecutionMode<T> {
match self {
One(f) => One(f.await),
Optional(Some(f)) => Optional(Some(f.await)),
Optional(None) => Optional(None),
Collection(l) => Collection(futures::future::join_all(l).await),
}
}
}
Loading

0 comments on commit 07bbf0a

Please sign in to comment.