implement DSL transpiler

main
anna 1 year ago
parent 3f4ef51c5d
commit 213693b816
Signed by: fef
GPG Key ID: EC22E476DC2D3D84

@ -24,6 +24,7 @@ pub trait Render<B: Backend> {
/// The backend we're compiling against.
pub type TargetBackend = StubBackend;
/// Stub backend for testing.
pub struct StubBackend;
impl Backend for StubBackend {
type Context = ();
@ -33,3 +34,18 @@ impl Backend for StubBackend {
"stub"
}
}
// FIXME: We can't write a generic impl of Render<StubBackend> for any T that
// is View<StubBackend> because it would result in a cyclic dependency
// (Render<T> is a supertrait of View<T>)
macro_rules! stub_render_impl {
($name:ident) => {
impl Render<StubBackend> for crate::view::$name {
fn render(&self, _context: ()) {}
}
};
}
stub_render_impl!(Button);
stub_render_impl!(Text);
stub_render_impl!(VStack);
stub_render_impl!(HStack);

@ -0,0 +1,190 @@
use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, TokenStream, TokenTree};
use crate::parser::{Component, Name, Param};
use crate::TtIter;
impl Component {
pub(crate) fn compile(self) -> impl TtIter {
let span = self.name.span();
self.name
.clone()
.into_constructor(self.children.is_some())
.chain([Group::new(
Delimiter::Parenthesis,
TokenStream::from_iter(
syms::id_new_call(span)
.chain([
Punct::new(',', Spacing::Alone).into(),
Self::compile_params(self.name, self.params),
])
.chain(self.children.into_iter().flat_map(|children| {
[
Punct::new(',', Spacing::Alone).into(),
Self::compile_children(children),
Punct::new('.', Spacing::Alone).into(),
Ident::new("into", span).into(),
Group::new(Delimiter::Parenthesis, TokenStream::new()).into(),
]
.into_iter()
})),
),
)
.into()])
}
fn compile_params(name: Name, params: Option<Vec<Param>>) -> TokenTree {
let span = name.span();
Group::new(
Delimiter::Brace,
TokenStream::from_iter(
[
TokenTree::Ident(Ident::new("let", span)),
Ident::new("mut", span).into(),
Ident::new("params", span).into(),
Punct::new('=', Spacing::Alone).into(),
]
.into_iter()
.chain(name.into_params_type())
.chain([
Punct::new(':', Spacing::Joint).into(),
Punct::new(':', Spacing::Alone).into(),
Ident::new("default", span).into(),
Group::new(Delimiter::Parenthesis, TokenStream::new()).into(),
Punct::new(';', Spacing::Alone).into(),
])
.chain(
params
.unwrap_or(Vec::new())
.into_iter()
.flat_map(|param| param.compile(Ident::new("params", span))),
)
.chain([Ident::new("params", span).into()]),
),
)
.into()
}
fn compile_children(children: Vec<Component>) -> TokenTree {
Group::new(
Delimiter::Bracket,
TokenStream::from_iter(children.into_iter().flat_map(|child| {
child
.compile()
.chain([Punct::new(',', Spacing::Alone).into()])
})),
)
.into()
}
}
impl Name {
/// `Name -> <Name as ::uwui::view::NewView>::new_dyn` (!group)
/// `Name -> <Name as ::uwui::view::NewViewGroup>::new_group_dyn` (group)
fn into_constructor(self, group: bool) -> impl TtIter {
let span = self.span();
let trait_name = if group { "NewViewGroup" } else { "NewView" };
let fn_name = if group { "new_group_dyn" } else { "new_dyn" };
[TokenTree::Punct(Punct::new('<', Spacing::Alone))]
.into_iter()
.chain(self.0.into_iter())
.chain([Ident::new("as", span).into()])
.chain(syms::absolute_path(span, ["view", trait_name].into_iter()))
.chain([
Punct::new('>', Spacing::Joint).into(),
Punct::new(':', Spacing::Joint).into(),
Punct::new(':', Spacing::Alone).into(),
Ident::new(fn_name, span).into(),
])
}
/// `Name -> <Name as ::uwui::view::NewView>::Params`
fn into_params_type(self) -> impl TtIter {
let span = self.span();
self.into_new_view().chain([
Punct::new(':', Spacing::Joint).into(),
Punct::new(':', Spacing::Alone).into(),
Ident::new("Params", span).into(),
])
}
/// `Name -> <Name as ::uwui::view::NewView>`
fn into_new_view(self) -> impl TtIter {
let span = self.span();
[Punct::new('<', Spacing::Alone).into()]
.into_iter()
.chain(self.0.into_iter())
.chain([Ident::new("as", span).into()])
.chain(syms::new_view(span))
.chain([Punct::new('>', Spacing::Joint).into()])
}
}
impl Param {
/// `name: expr -> ident.name = (expr).into();`
fn compile(self, ident: Ident) -> impl TtIter {
let (name, colon, expr) = (self.0, self.1, self.2);
let expr_span = expr.span();
let mut eq = Punct::new('=', Spacing::Alone);
eq.set_span(colon.span());
[
ident.into(),
Punct::new('.', Spacing::Alone).into(),
name,
eq.into(),
Group::new(
Delimiter::Parenthesis,
TokenStream::from_iter(expr.0.into_iter()),
)
.into(),
Punct::new('.', Spacing::Alone).into(),
Ident::new("into", expr_span).into(),
Group::new(Delimiter::Parenthesis, TokenStream::new()).into(),
Punct::new(';', Spacing::Alone).into(),
]
.into_iter()
}
}
/// Helpers for static identifiers within our API
mod syms {
use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream};
use crate::TtIter;
/// `::uwui::view::Id::new()`
pub(super) fn id_new_call(span: Span) -> impl TtIter {
absolute_path(span, ["view", "Id", "new"].into_iter()).chain([Group::new(
Delimiter::Parenthesis,
TokenStream::new(),
)
.into()])
}
/// `::uwui::view::NewView`
pub(super) fn new_view(span: Span) -> impl TtIter {
absolute_path(span, ["view", "NewView"].into_iter())
}
pub(super) fn absolute_path<'a>(
span: Span,
segments: impl Iterator<Item = &'a str> + 'a,
) -> impl TtIter + 'a {
[Ident::new("crate", span).into()]
.into_iter()
.chain(segments.flat_map(move |name| {
[
Punct::new(':', Spacing::Joint).into(),
Punct::new(':', Spacing::Alone).into(),
Ident::new(name, span).into(),
]
.into_iter()
}))
}
}

@ -1,8 +1,12 @@
#![feature(proc_macro_diagnostic)]
#![feature(proc_macro_span)]
use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
pub(crate) trait TtIter: Iterator<Item = TokenTree> {}
impl<T> TtIter for T where T: Iterator<Item = TokenTree> {}
mod compiler;
/// Syntax tree parser.
mod parser;
@ -12,17 +16,31 @@ use parser::Parser;
pub fn template(input: TokenStream) -> TokenStream {
let mut stream = input.into_iter().peekable();
let parser = Parser::from(&mut stream);
let array = TokenTree::Group(Group::new(
Delimiter::Bracket,
TokenStream::from_iter(parser.flat_map(|component| {
component
.compile()
.chain([Punct::new(',', Spacing::Alone).into()])
})),
));
for component in parser {
println!("{component:?}");
}
// stub, for now
TokenStream::from_iter(
[TokenTree::Group(Group::new(
Delimiter::Parenthesis,
TokenStream::new(),
))]
[
TokenTree::Punct(Punct::new(':', Spacing::Joint)),
Punct::new(':', Spacing::Alone).into(),
Ident::new("std", Span::call_site()).into(),
Punct::new(':', Spacing::Joint).into(),
Punct::new(':', Spacing::Alone).into(),
Ident::new("vec", Span::call_site()).into(),
Punct::new(':', Spacing::Joint).into(),
Punct::new(':', Spacing::Alone).into(),
Ident::new("Vec", Span::call_site()).into(),
Punct::new(':', Spacing::Joint).into(),
Punct::new(':', Spacing::Alone).into(),
Ident::new("from", Span::call_site()).into(),
Group::new(Delimiter::Parenthesis, array.into()).into(),
]
.into_iter(),
)
}

@ -1,33 +1,35 @@
use proc_macro::{Delimiter, Diagnostic, Level, Spacing, TokenTree};
use crate::TtIter;
use proc_macro::{Delimiter, Diagnostic, Level, Spacing, Span, TokenTree};
use std::{fmt, iter::Peekable};
/// A single component definition, including children.
pub struct Component {
pub(crate) struct Component {
/// The type name of this component (may be namespaced).
name: Name,
pub(crate) name: Name,
/// The parameter list as a dictionary, if `name` is followed by parentheses.
params: Option<Vec<Param>>,
pub(crate) params: Option<Vec<Param>>,
/// All child component definitions.
children: Option<Vec<Component>>,
pub(crate) children: Option<Vec<Component>>,
}
pub struct Name(Vec<TokenTree>);
pub struct Param(TokenTree, TokenTree, Expr);
pub struct Expr(Vec<TokenTree>);
#[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>);
/// 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>> {
pub(crate) struct Parser<'a, T: TtIter> {
stream: &'a mut Peekable<T>,
}
impl<'a, T: Iterator<Item = TokenTree>> From<&'a mut Peekable<T>> for Parser<'a, 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: Iterator<Item = TokenTree>> Iterator for Parser<'a, T> {
impl<'a, T: TtIter> Iterator for Parser<'a, T> {
type Item = Component;
fn next(&mut self) -> Option<Component> {
@ -35,7 +37,7 @@ impl<'a, T: Iterator<Item = TokenTree>> Iterator for Parser<'a, T> {
}
}
impl<'a, T: Iterator<Item = TokenTree>> Parser<'a, T> {
impl<'a, T: TtIter> Parser<'a, T> {
/// ```notrust
/// component = name *SP [ param-list *SP ] [ child-list *SP ]
/// param-list = "(" *SP *( param *SP "," *SP ) [ param *SP ] ")"
@ -104,7 +106,7 @@ impl Name {
/// ```notrust
/// name = [ path-sep ] ident *( path-sep ident )
/// ```
fn eat(stream: &mut Peekable<impl Iterator<Item = TokenTree>>) -> Option<Self> {
fn eat(stream: &mut Peekable<impl TtIter>) -> Option<Self> {
stream.peek()?;
let mut segments = Vec::new();
@ -127,12 +129,16 @@ impl Name {
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 = 2*":"
/// ```
fn parse_path_sep(
stream: &mut Peekable<impl Iterator<Item = TokenTree>>,
) -> Option<[TokenTree; 2]> {
fn parse_path_sep(stream: &mut Peekable<impl TtIter>) -> Option<[TokenTree; 2]> {
let first = stream.next_if(|tt| match tt {
TokenTree::Punct(punct) => punct.as_char() == ':' && punct.spacing() == Spacing::Joint,
_ => false,
@ -163,7 +169,7 @@ impl Param {
/// ```notrust
/// param = ident *SP ":" *SP expr
/// ```
fn parse(stream: &mut Peekable<impl Iterator<Item = TokenTree>>) -> Option<Self> {
fn parse(stream: &mut Peekable<impl TtIter>) -> Option<Self> {
stream.peek()?;
let ident = eat_ident(stream)?;
let colon = match stream.next() {
@ -193,7 +199,7 @@ 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> {
fn parse(stream: &mut Peekable<impl TtIter>) -> Option<Self> {
let mut tokens = Vec::new();
while let Some(tt) = stream.peek() {
@ -209,9 +215,15 @@ impl Expr {
Some(Expr(tokens))
}
}
pub(crate) fn span(&self) -> Span {
let first = self.0.first().unwrap().span();
let last = self.0.last().unwrap().span();
first.join(last).unwrap()
}
}
fn eat_ident(stream: &mut Peekable<impl Iterator<Item = TokenTree>>) -> Option<TokenTree> {
fn eat_ident(stream: &mut Peekable<impl TtIter>) -> Option<TokenTree> {
match stream.next() {
Some(tt) => match tt {
TokenTree::Ident(_) => Some(tt),

Loading…
Cancel
Save