add template macro crate and parser
parent
b3a8f18e74
commit
35db908fcd
@ -1,3 +1,4 @@
|
||||
/target
|
||||
/uwui_dsl/target
|
||||
/.idea
|
||||
/.vscode
|
||||
|
@ -1,2 +1,13 @@
|
||||
pub mod backend;
|
||||
pub mod view;
|
||||
|
||||
pub use uwui_dsl::template;
|
||||
|
||||
fn asdf() {
|
||||
let _ = template!(
|
||||
VStack(gravity: Gravity::Center, homogay: true) {
|
||||
Text(content: "hello, world")
|
||||
Button(label: "don't click me")
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "uwui_dsl"
|
||||
version = "0.1.0"
|
@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "uwui_dsl"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
@ -0,0 +1,28 @@
|
||||
#![feature(proc_macro_diagnostic)]
|
||||
#![feature(proc_macro_span)]
|
||||
|
||||
use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
|
||||
|
||||
/// Syntax tree parser.
|
||||
mod parser;
|
||||
|
||||
use parser::Parser;
|
||||
|
||||
#[proc_macro]
|
||||
pub fn template(input: TokenStream) -> TokenStream {
|
||||
let mut stream = input.into_iter().peekable();
|
||||
let parser = Parser::from(&mut stream);
|
||||
|
||||
for component in parser {
|
||||
println!("{component:?}");
|
||||
}
|
||||
|
||||
// stub, for now
|
||||
TokenStream::from_iter(
|
||||
[TokenTree::Group(Group::new(
|
||||
Delimiter::Parenthesis,
|
||||
TokenStream::new(),
|
||||
))]
|
||||
.into_iter(),
|
||||
)
|
||||
}
|
@ -0,0 +1,295 @@
|
||||
use proc_macro::{Delimiter, Diagnostic, Level, Spacing, TokenTree};
|
||||
use std::{fmt, iter::Peekable};
|
||||
|
||||
/// A single component definition, including children.
|
||||
pub struct Component {
|
||||
/// The type name of this component (may be namespaced).
|
||||
name: Name,
|
||||
/// The parameter list as a dictionary, if `name` is followed by parentheses.
|
||||
params: Option<Vec<Param>>,
|
||||
/// All child component definitions.
|
||||
children: Option<Vec<Component>>,
|
||||
}
|
||||
|
||||
pub struct Name(Vec<TokenTree>);
|
||||
pub struct Param(TokenTree, TokenTree, Expr);
|
||||
pub struct Expr(Vec<TokenTree>);
|
||||
|
||||
/// The main syntax tree parser. This implements [`Iterator`], so it will
|
||||
/// only parse (and consume the input iterator) when consumed itself.
|
||||
pub struct Parser<'a, T: Iterator<Item = TokenTree>> {
|
||||
stream: &'a mut Peekable<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: Iterator<Item = TokenTree>> From<&'a mut Peekable<T>> for Parser<'a, T> {
|
||||
fn from(stream: &'a mut Peekable<T>) -> Self {
|
||||
Self { stream }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Iterator<Item = TokenTree>> Iterator for Parser<'a, T> {
|
||||
type Item = Component;
|
||||
|
||||
fn next(&mut self) -> Option<Component> {
|
||||
self.parse_component()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Iterator<Item = TokenTree>> Parser<'a, T> {
|
||||
/// ```notrust
|
||||
/// component = name *SP [ param-list *SP ] [ child-list *SP ]
|
||||
/// param-list = "(" *SP *( param *SP "," *SP ) [ param *SP ] ")"
|
||||
/// child-list = "{" *SP *( component ) "}"
|
||||
/// ```
|
||||
fn parse_component(&mut self) -> Option<Component> {
|
||||
self.stream.peek()?;
|
||||
let name = Name::eat(self.stream)?;
|
||||
|
||||
let params = self
|
||||
.stream
|
||||
.next_if(Self::is_param_list)
|
||||
.map(|tt| match tt {
|
||||
TokenTree::Group(group) => group.stream().into_iter().peekable(),
|
||||
_ => unreachable!("is_param_list 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(|tt| match tt {
|
||||
TokenTree::Punct(punct) => {
|
||||
punct.as_char() == ',' && punct.spacing() == Spacing::Alone
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
}
|
||||
if let Some(tt) = stream.peek() {
|
||||
Diagnostic::spanned(tt.span(), Level::Error, "Unexpected token").emit();
|
||||
}
|
||||
list
|
||||
});
|
||||
|
||||
let children = self
|
||||
.stream
|
||||
.next_if(Self::is_child_list)
|
||||
.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 {
|
||||
name,
|
||||
params,
|
||||
children,
|
||||
})
|
||||
}
|
||||
|
||||
fn is_param_list(tt: &TokenTree) -> bool {
|
||||
match tt {
|
||||
TokenTree::Group(group) => group.delimiter() == Delimiter::Parenthesis,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_child_list(tt: &TokenTree) -> bool {
|
||||
match tt {
|
||||
TokenTree::Group(group) => group.delimiter() == Delimiter::Brace,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Name {
|
||||
/// ```notrust
|
||||
/// name = [ path-sep ] ident *( path-sep ident )
|
||||
/// ```
|
||||
fn eat(stream: &mut Peekable<impl Iterator<Item = TokenTree>>) -> 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))
|
||||
}
|
||||
|
||||
/// ```notrust
|
||||
/// path-sep = 2*":"
|
||||
/// ```
|
||||
fn parse_path_sep(
|
||||
stream: &mut Peekable<impl Iterator<Item = TokenTree>>,
|
||||
) -> Option<[TokenTree; 2]> {
|
||||
let first = stream.next_if(|tt| match tt {
|
||||
TokenTree::Punct(punct) => punct.as_char() == ':' && punct.spacing() == Spacing::Joint,
|
||||
_ => false,
|
||||
})?;
|
||||
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, "Unexpected token").emit();
|
||||
None
|
||||
}
|
||||
},
|
||||
None => {
|
||||
Diagnostic::new(Level::Error, "Unexpected end of stream1").emit();
|
||||
None
|
||||
}
|
||||
}?;
|
||||
|
||||
Some([first, second])
|
||||
}
|
||||
}
|
||||
|
||||
impl Param {
|
||||
/// ```notrust
|
||||
/// param = ident *SP ":" *SP expr
|
||||
/// ```
|
||||
fn parse(stream: &mut Peekable<impl Iterator<Item = TokenTree>>) -> 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 stream2").emit();
|
||||
None
|
||||
}
|
||||
}?;
|
||||
let expr = Expr::parse(stream)?;
|
||||
|
||||
Some(Param(ident, colon, expr))
|
||||
}
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
/// ```notrust
|
||||
/// expr = it's just rust code until we hit a comma, stop being a pedantic bitch about it
|
||||
/// ```
|
||||
fn parse(stream: &mut Peekable<impl Iterator<Item = TokenTree>>) -> Option<Self> {
|
||||
let mut tokens = Vec::new();
|
||||
|
||||
while let Some(tt) = stream.peek() {
|
||||
match tt {
|
||||
TokenTree::Punct(punct) if punct.as_char() == ',' => break,
|
||||
_ => tokens.push(stream.next().expect("We already peeked this")),
|
||||
}
|
||||
}
|
||||
|
||||
if tokens.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Expr(tokens))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn eat_ident(stream: &mut Peekable<impl Iterator<Item = TokenTree>>) -> Option<TokenTree> {
|
||||
match stream.next() {
|
||||
Some(tt) => match tt {
|
||||
TokenTree::Ident(_) => Some(tt),
|
||||
_ => {
|
||||
Diagnostic::spanned(tt.span(), Level::Error, "Expected an identifier").emit();
|
||||
None
|
||||
}
|
||||
},
|
||||
None => {
|
||||
Diagnostic::new(Level::Error, "Unexpected end of stream").emit();
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////// 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(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue