You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
389 lines
11 KiB
Rust
389 lines
11 KiB
Rust
use crate::TtIter;
|
|
use proc_macro::{
|
|
Delimiter, Diagnostic, Group, Ident, Level, Spacing, Span, TokenStream, TokenTree,
|
|
};
|
|
use std::{fmt, iter::Peekable};
|
|
|
|
/// A single component definition, including children.
|
|
pub(crate) struct Component {
|
|
/// Event listeners attached to this component.
|
|
pub(crate) listeners: Vec<Listener>,
|
|
/// The type name of this component (may be namespaced).
|
|
pub(crate) name: Name,
|
|
/// The parameter list as a dictionary, if `name` is followed by parentheses.
|
|
pub(crate) params: Option<Vec<Param>>,
|
|
/// All child component definitions.
|
|
pub(crate) children: Option<Vec<Component>>,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) struct Name(pub(crate) Vec<TokenTree>);
|
|
pub(crate) struct Param(pub(crate) TokenTree, pub(crate) TokenTree, pub(crate) Expr);
|
|
pub(crate) struct Expr(pub(crate) Vec<TokenTree>);
|
|
pub(crate) struct Listener {
|
|
pub(crate) at: TokenTree,
|
|
pub(crate) name: TokenTree,
|
|
pub(crate) callback: TokenTree,
|
|
}
|
|
|
|
/// The main syntax tree parser. This implements [`Iterator`], so it will
|
|
/// only parse (and consume the input iterator) when consumed itself.
|
|
pub(crate) struct Parser<'a, T: TtIter> {
|
|
stream: &'a mut Peekable<T>,
|
|
}
|
|
|
|
impl<'a, T: TtIter> From<&'a mut Peekable<T>> for Parser<'a, T> {
|
|
fn from(stream: &'a mut Peekable<T>) -> Self {
|
|
Self { stream }
|
|
}
|
|
}
|
|
|
|
impl<'a, T: TtIter> Iterator for Parser<'a, T> {
|
|
type Item = Component;
|
|
|
|
fn next(&mut self) -> Option<Component> {
|
|
self.parse_component()
|
|
}
|
|
}
|
|
|
|
impl<'a, T: TtIter> Parser<'a, T> {
|
|
/// ```notrust
|
|
/// component = *listener name space [ param-list space ] [ child-list space ]
|
|
/// param-list = "(" space *( param space "," space ) [ param space ] ")"
|
|
/// child-list = "{" space *( component ) "}"
|
|
/// ```
|
|
fn parse_component(&mut self) -> Option<Component> {
|
|
self.stream.peek()?;
|
|
|
|
let mut listeners = Vec::new();
|
|
while let Some(listener) = Listener::parse(self.stream) {
|
|
Diagnostic::spanned(
|
|
listener.span(),
|
|
Level::Error,
|
|
"Event listeners aren't implemented yet",
|
|
)
|
|
.emit();
|
|
listeners.push(listener);
|
|
}
|
|
|
|
let name = Name::eat(self.stream)?;
|
|
|
|
let params = self
|
|
.stream
|
|
.next_if(is_group(Delimiter::Parenthesis))
|
|
.map(|tt| match tt {
|
|
TokenTree::Group(group) => group.stream().into_iter().peekable(),
|
|
_ => unreachable!("is_group() already matched this"),
|
|
})
|
|
.map(|mut stream| {
|
|
let mut list = Vec::new();
|
|
while let Some(param) = Param::parse(&mut stream) {
|
|
list.push(param);
|
|
stream.next_if(is_punct(','));
|
|
}
|
|
if let Some(tt) = stream.peek() {
|
|
Diagnostic::spanned(tt.span(), Level::Error, "Unexpected token").emit();
|
|
}
|
|
list
|
|
});
|
|
|
|
let children = self
|
|
.stream
|
|
.next_if(is_group(Delimiter::Brace))
|
|
.map(|tt| match tt {
|
|
TokenTree::Group(group) => group.stream().into_iter().peekable(),
|
|
_ => unreachable!("is_child_list already matched this"),
|
|
})
|
|
.map(|mut stream| Parser::from(&mut stream).collect());
|
|
|
|
Some(Component {
|
|
listeners,
|
|
name,
|
|
params,
|
|
children,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Name {
|
|
/// ```notrust
|
|
/// name = [ path-sep space ] ident *( space path-sep space ident )
|
|
/// ```
|
|
fn eat(stream: &mut Peekable<impl TtIter>) -> Option<Self> {
|
|
stream.peek()?;
|
|
let mut segments = Vec::new();
|
|
|
|
// [ path-sep ]
|
|
segments.extend(
|
|
Self::parse_path_sep(stream)
|
|
.into_iter()
|
|
.flat_map(|sep| sep.into_iter()),
|
|
);
|
|
|
|
// ident
|
|
segments.push(eat_ident(stream)?);
|
|
|
|
// *( path-sep ident )
|
|
while let Some(sep) = Self::parse_path_sep(stream) {
|
|
segments.extend(sep.into_iter());
|
|
segments.push(eat_ident(stream)?);
|
|
}
|
|
|
|
Some(Name(segments))
|
|
}
|
|
|
|
pub(crate) fn span(&self) -> Span {
|
|
let first = self.0.first().unwrap().span();
|
|
let last = self.0.last().unwrap().span();
|
|
first.join(last).unwrap()
|
|
}
|
|
|
|
/// ```notrust
|
|
/// path-sep = "::"
|
|
/// ```
|
|
fn parse_path_sep(stream: &mut Peekable<impl TtIter>) -> Option<[TokenTree; 2]> {
|
|
let first = stream.next_if(is_punct_spaced(':', Spacing::Joint))?;
|
|
let second = match stream.next() {
|
|
Some(tt) => match &tt {
|
|
TokenTree::Punct(punct)
|
|
if punct.as_char() == ':' && punct.spacing() == Spacing::Alone =>
|
|
{
|
|
Some(tt)
|
|
}
|
|
_ => {
|
|
Diagnostic::spanned(tt.span(), Level::Error, "Expected a `:`").emit();
|
|
None
|
|
}
|
|
},
|
|
None => {
|
|
Diagnostic::new(Level::Error, "Unexpected end of stream").emit();
|
|
None
|
|
}
|
|
}?;
|
|
|
|
Some([first, second])
|
|
}
|
|
}
|
|
|
|
impl Param {
|
|
/// ```notrust
|
|
/// param = ident space ":" space expr
|
|
/// ```
|
|
fn parse(stream: &mut Peekable<impl TtIter>) -> Option<Self> {
|
|
stream.peek()?;
|
|
let ident = eat_ident(stream)?;
|
|
let colon = match stream.next() {
|
|
Some(tt) => match &tt {
|
|
TokenTree::Punct(punct)
|
|
if punct.as_char() == ':' && punct.spacing() == Spacing::Alone =>
|
|
{
|
|
Some(tt)
|
|
}
|
|
_ => {
|
|
Diagnostic::spanned(tt.span(), Level::Error, "Expected a `:`").emit();
|
|
None
|
|
}
|
|
},
|
|
None => {
|
|
Diagnostic::new(Level::Error, "Unexpected end of stream").emit();
|
|
None
|
|
}
|
|
}?;
|
|
let expr = Expr::parse(stream)?;
|
|
|
|
Some(Param(ident, colon, expr))
|
|
}
|
|
}
|
|
|
|
impl Expr {
|
|
/// ```notrust
|
|
/// expr = <now that's just rust code, stop being a pedantic bitch about it>
|
|
/// ```
|
|
fn parse(stream: &mut Peekable<impl TtIter>) -> Option<Self> {
|
|
let mut tokens = Vec::new();
|
|
|
|
while let Some(tt) = stream.peek() {
|
|
if is_punct(',')(tt) {
|
|
break;
|
|
} else {
|
|
tokens.push(check_ident_recurse(
|
|
stream.next().expect("We already peeked this"),
|
|
));
|
|
}
|
|
}
|
|
|
|
if tokens.is_empty() {
|
|
None
|
|
} else {
|
|
Some(Expr(tokens))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Listener {
|
|
/// ```notrust
|
|
/// listener = "@" ident space "(" space expr space ")" space
|
|
/// ```
|
|
fn parse(stream: &mut Peekable<impl TtIter>) -> Option<Self> {
|
|
let at = stream.next_if(is_punct('@'))?;
|
|
let name = eat_ident(stream)?;
|
|
let callback = match stream.next_if(is_group(Delimiter::Parenthesis)) {
|
|
Some(tt) => tt,
|
|
None => {
|
|
Diagnostic::spanned(
|
|
at.span().join(name.span()).unwrap(),
|
|
Level::Error,
|
|
"Event listeners require a callback",
|
|
)
|
|
.emit();
|
|
return None;
|
|
}
|
|
};
|
|
|
|
Some(Self { at, name, callback })
|
|
}
|
|
|
|
fn span(&self) -> Span {
|
|
self.at.span().join(self.callback.span()).unwrap()
|
|
}
|
|
}
|
|
|
|
/// ```notrust
|
|
/// ident = <any valid rust identifier, excluding those starting with "__uwui">
|
|
/// ```
|
|
fn eat_ident(stream: &mut Peekable<impl TtIter>) -> Option<TokenTree> {
|
|
match stream.next() {
|
|
Some(tt) => match &tt {
|
|
TokenTree::Ident(i) => {
|
|
check_ident(i);
|
|
Some(tt)
|
|
}
|
|
_ => {
|
|
Diagnostic::spanned(tt.span(), Level::Error, "Expected an identifier").emit();
|
|
None
|
|
}
|
|
},
|
|
None => {
|
|
Diagnostic::new(Level::Error, "Unexpected end of stream").emit();
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
fn check_ident_recurse(tt: TokenTree) -> TokenTree {
|
|
match tt {
|
|
TokenTree::Ident(ref ident) => {
|
|
check_ident(ident);
|
|
tt
|
|
}
|
|
TokenTree::Group(group) => {
|
|
let mut wrapped = Group::new(
|
|
group.delimiter(),
|
|
TokenStream::from_iter(group.stream().into_iter().map(check_ident_recurse)),
|
|
);
|
|
wrapped.set_span(group.span());
|
|
TokenTree::from(wrapped)
|
|
}
|
|
_ => tt,
|
|
}
|
|
}
|
|
|
|
fn check_ident(ident: &Ident) {
|
|
if ident.to_string().starts_with("__uwui") {
|
|
Diagnostic::spanned(
|
|
ident.span(),
|
|
Level::Error,
|
|
"Identifiers starting with `__uwui` are reserved",
|
|
)
|
|
.emit();
|
|
}
|
|
}
|
|
|
|
const fn is_punct_spaced(c: char, s: Spacing) -> impl FnMut(&TokenTree) -> bool {
|
|
move |tt| match tt {
|
|
TokenTree::Punct(punct) => punct.as_char() == c && punct.spacing() == s,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
fn is_punct(c: char) -> impl FnMut(&TokenTree) -> bool {
|
|
move |tt| match tt {
|
|
TokenTree::Punct(punct) => punct.as_char() == c,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
fn is_group(delim: Delimiter) -> impl FnMut(&TokenTree) -> bool {
|
|
move |tt| match tt {
|
|
TokenTree::Group(group) => group.delimiter() == delim,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
///////////// debug stuff ///////////////
|
|
|
|
impl Component {
|
|
fn print(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
|
let indent = " ".repeat(depth);
|
|
|
|
f.write_str(&indent)?;
|
|
fmt::Debug::fmt(&self.name, f)?;
|
|
if let Some(params) = &self.params {
|
|
f.write_str("(")?;
|
|
let mut first = true;
|
|
for param in params {
|
|
if first {
|
|
first = false;
|
|
} else {
|
|
f.write_str(", ")?;
|
|
}
|
|
fmt::Debug::fmt(param, f)?;
|
|
}
|
|
f.write_str(")")?;
|
|
}
|
|
f.write_str("\n")?;
|
|
|
|
if let Some(children) = &self.children {
|
|
for child in children {
|
|
child.print(f, depth + 1)?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for Component {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
f.write_str("\n")?;
|
|
self.print(f, 0)?;
|
|
f.write_str("\n")
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for Name {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
for segment in &self.0 {
|
|
f.write_str(&segment.to_string())?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for Param {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
f.write_str(&self.0.to_string())?;
|
|
f.write_str(" => ")?;
|
|
fmt::Debug::fmt(&self.2, f)
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for Expr {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
for tt in &self.0 {
|
|
f.write_str(&tt.to_string())?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|