Skip to content

Commit

Permalink
add selector filter to language (unimplemented yet)
Browse files Browse the repository at this point in the history
  • Loading branch information
suaviloquence committed Aug 5, 2024
1 parent 3031319 commit 4eb486e
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 31 deletions.
8 changes: 7 additions & 1 deletion grammar.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,15 @@ qualifier -> `?`
| `*`
| ""

filter_list -> `|` ID `(` arg_list `)` qualifier filter_list
filter_list -> `|` filter qualifier filter_list
| ""

value -> leaf
| inline

filter -> ID `(` arg_list `)`
| `[` ID `:` value `]`

arg_list -> ID `:` leaf arg_list2
| ""
# allow optional trailing comma
Expand Down
25 changes: 22 additions & 3 deletions src/frontend/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,12 @@ pub struct Inline<'a> {
pub filters: Option<AstRef<'a, FilterList<'a>>>,
}

#[derive(Debug, Clone)]
pub enum Value<'a> {
Leaf(Leaf<'a>),
Inline(Inline<'a>),
}

#[derive(Debug, Clone)]
pub struct Statement<'a> {
pub id: &'a str,
Expand All @@ -224,6 +230,12 @@ pub struct Element<'a> {
pub statements: Option<AstRef<'a, StatementList<'a>>>,
}

#[derive(Debug, Clone)]
pub enum Filter<'a> {
Call(FilterCall<'a>),
Select(FilterSelect<'a>),
}

ast_enum! {
#![derive(Debug, Clone)]
pub enum Ast<'a> {
Expand All @@ -238,12 +250,19 @@ pub enum Ast<'a> {
value: Leaf<'a>,
next: Option<AstRef<'a, ArgList<'a>>>,
},
@flatten[self, .next]
FilterList {
FilterCall {
id: &'a str,
args: Option<AstRef<'a, ArgList<'a>>>,
next: Option<AstRef<'a, FilterList<'a>>>,
},
FilterSelect {
name: &'a str,
value: Value<'a>,
},
@flatten[self, .next]
FilterList {
filter: Filter<'a>,
qualifier: Qualifier,
next: Option<AstRef<'a, FilterList<'a>>>,
},
@flatten[self, .next]
StatementList {
Expand Down
76 changes: 67 additions & 9 deletions src/frontend/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use std::borrow::Cow;
use super::{
arena::Arena,
ast::{
ArgList, Ast, AstRef, Element, FilterList, Inline, Leaf, Qualifier, RValue, Selector,
SelectorCombinator, SelectorList, Statement, StatementList,
ArgList, Ast, AstRef, Element, Filter, FilterCall, FilterList, FilterSelect, Inline, Leaf,
Qualifier, RValue, Selector, SelectorCombinator, SelectorList, Statement, StatementList,
Value,
},
scanner::{Lexeme, Scanner, Span, Token},
};
Expand Down Expand Up @@ -191,6 +192,28 @@ impl<'a> Parser<'a> {
Ok(Inline { value, filters })
}

fn parse_value(&mut self) -> Result<Value<'a>> {
let (span, lx) = self.scanner.peek_non_whitespace();

match lx.token {
Token::Less => self.parse_inline().map(Value::Inline),
Token::Dollar | Token::Int | Token::Float | Token::String => {
self.parse_leaf().map(Value::Leaf)
}
_ => Err(ParseError::unexpected(
vec![
Token::Less,
Token::Dollar,
Token::Int,
Token::Float,
Token::String,
],
lx,
span,
)),
}
}

fn parse_selector_list(&mut self) -> Result<Option<AstRef<'a, SelectorList<'a>>>> {
let mut item = self.scanner.peek_non_comment();
if item.1.token == Token::Whitespace {
Expand Down Expand Up @@ -280,21 +303,45 @@ impl<'a> Parser<'a> {
let (_, lx) = self.scanner.peek_non_whitespace();
if lx.token == Token::Pipe {
self.scanner.eat_token();
let id = self.try_eat(Token::Id)?.value;
self.try_eat(Token::ParenOpen)?;
let args = self.parse_arg_list()?;
self.try_eat(Token::ParenClose)?;
let filter = self.parse_filter()?;
let next = self.parse_filter_list()?;
let qualifier = self.parse_qualifier()?;
let r = self
.arena
.insert_variant(FilterList::new(id, args, next, qualifier));
.insert_variant(FilterList::new(filter, qualifier, next));
Ok(Some(r))
} else {
Ok(None)
}
}

fn parse_filter(&mut self) -> Result<Filter<'a>> {
let (span, lx) = self.scanner.peek_non_whitespace();
self.scanner.eat_token();

match lx.token {
Token::Id => {
let id = lx.value;
self.try_eat(Token::ParenOpen)?;
let args = self.parse_arg_list()?;
self.try_eat(Token::ParenClose)?;
Ok(Filter::Call(FilterCall::new(id, args)))
}
Token::BracketOpen => {
let name = self.try_eat(Token::Id)?.value;
self.try_eat(Token::Colon)?;
let value = self.parse_value()?;
self.try_eat(Token::BracketClose)?;
Ok(Filter::Select(FilterSelect::new(name, value)))
}
_ => Err(ParseError::unexpected(
vec![Token::Id, Token::BracketOpen],
lx,
span,
)),
}
}

fn parse_arg_list(&mut self) -> Result<Option<AstRef<'a, ArgList<'a>>>> {
let (span, lx) = self.scanner.peek_non_whitespace();
match lx.token {
Expand Down Expand Up @@ -461,12 +508,23 @@ mod tests {
assert!(
matches!(
&filters[..],
[FilterList { id: "cat", .. }, FilterList { id: "meow", .. }]
[
FilterList {
filter: Filter::Call(FilterCall { id: "cat", .. }),
..
},
FilterList {
filter: Filter::Call(FilterCall { id: "meow", .. }),
..
}
]
),
"found {filters:?}"
);

let filter = filters[0];
let Filter::Call(filter) = &filters[0].filter else {
unreachable!("Validated as Filter::Call above");
};
let args = arena.flatten(filter.args);
assert!(
matches!(
Expand Down
10 changes: 10 additions & 0 deletions src/frontend/scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ pub enum Token {
Semi,
/// a less than sign `<` to indicate the start of an inline expansion
Less,
/// an opening bracket `[` to indicate the start of a select filter
/// or CSS attribute selector
BracketOpen,
/// a closing bracket `]` to indicate the end of a select filter
/// or CSS attribute selector
BracketClose,
/// A single-line comment that begins with two forward slashes '//' and
/// spans the rest of the line
Comment,
Expand Down Expand Up @@ -103,6 +109,8 @@ mod statics {
Colon <- ":"
Semi <- ";"
Less <- "<"
BracketOpen <- r"\["
BracketClose <- r"\]"
Comment <- r"//[^\n]*"
};
}
Expand Down Expand Up @@ -271,6 +279,8 @@ mod tests {
Plus => "+"
Question => "?"
Pipe => "|"
BracketOpen => "["
BracketClose => "]"
}
}

Expand Down
44 changes: 26 additions & 18 deletions src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ use reqwest::Url;
use value::{EValue, ListIter};

use crate::frontend::{
ast::{AstRef, Element, FilterList, Inline, Leaf, Qualifier, RValue, Statement, StatementList},
ast::{
self, AstRef, Element, FilterList, Inline, Leaf, Qualifier, RValue, Statement,
StatementList,
},
AstArena,
};

Expand Down Expand Up @@ -243,23 +246,28 @@ impl<'ast> Interpreter<'ast> {
ctx: &mut ElementContext<'ast, 'ctx>,
) -> Result<EValue<'ctx>> {
filters
.try_fold(value.into(), |value, filter| {
let args = self
.ast
.flatten(filter.args)
.into_iter()
.map(|arg| Ok((arg.id, ctx.leaf_to_value(&arg.value)?)))
.collect::<Result<BTreeMap<_, _>>>()?;

match filter.qualifier {
Qualifier::One => filter::dispatch_filter(filter.id, value, args, ctx),
Qualifier::Optional if matches!(value, Value::Null) => Ok(Value::Null),
Qualifier::Optional => filter::dispatch_filter(filter.id, value, args, ctx),
Qualifier::Collection => value
.try_unwrap::<ListIter>()?
.map(|value| filter::dispatch_filter(filter.id, value, args.clone(), ctx))
.collect::<Result<Vec<_>>>()
.map(Value::List),
.try_fold(value.into(), |value, filter| match &filter.filter {
ast::Filter::Call(call) => {
let args = self
.ast
.flatten(call.args)
.into_iter()
.map(|arg| Ok((arg.id, ctx.leaf_to_value(&arg.value)?)))
.collect::<Result<BTreeMap<_, _>>>()?;

match filter.qualifier {
Qualifier::One => filter::dispatch_filter(call.id, value, args, ctx),
Qualifier::Optional if matches!(value, Value::Null) => Ok(Value::Null),
Qualifier::Optional => filter::dispatch_filter(call.id, value, args, ctx),
Qualifier::Collection => value
.try_unwrap::<ListIter>()?
.map(|value| filter::dispatch_filter(call.id, value, args.clone(), ctx))
.collect::<Result<Vec<_>>>()
.map(Value::List),
}
}
ast::Filter::Select(_select) => {
todo!()
}
})
.map(EValue::from)
Expand Down

0 comments on commit 4eb486e

Please sign in to comment.