mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-02 22:54:58 +00:00
use quote! macro to generate grammar
We already use syn"e elsewhere (transitively), so it make sense to cut down on the number of technologies and get rid of tera
This commit is contained in:
parent
d545a5c75c
commit
8cefdb5527
4 changed files with 181 additions and 40 deletions
|
@ -1,27 +1,162 @@
|
|||
use std::{collections::BTreeMap, fs, path::Path};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fs,
|
||||
io::Write,
|
||||
path::Path,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
use quote::quote;
|
||||
use heck::{ShoutySnakeCase, SnakeCase};
|
||||
use quote::{format_ident, quote};
|
||||
use ron;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{project_root, Mode, Result, AST, GRAMMAR};
|
||||
use crate::{project_root, Mode, Result, AST, GRAMMAR, SYNTAX_KINDS};
|
||||
|
||||
pub fn generate(mode: Mode) -> Result<()> {
|
||||
let grammar = project_root().join(GRAMMAR);
|
||||
// let syntax_kinds = project_root().join(SYNTAX_KINDS);
|
||||
let ast = project_root().join(AST);
|
||||
generate_ast(&grammar, &ast, mode)
|
||||
}
|
||||
|
||||
fn generate_ast(grammar_src: &Path, dst: &Path, mode: Mode) -> Result<()> {
|
||||
let src: Grammar = {
|
||||
let text = fs::read_to_string(grammar_src)?;
|
||||
let grammar: Grammar = {
|
||||
let text = fs::read_to_string(grammar)?;
|
||||
ron::de::from_str(&text)?
|
||||
};
|
||||
eprintln!("{:#?}", src);
|
||||
|
||||
let _syntax_kinds = project_root().join(SYNTAX_KINDS);
|
||||
let _ast = project_root().join(AST);
|
||||
|
||||
let ast = generate_ast(&grammar)?;
|
||||
println!("{}", ast);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_ast(grammar: &Grammar) -> Result<String> {
|
||||
let nodes = grammar.ast.iter().map(|(name, ast_node)| {
|
||||
let variants =
|
||||
ast_node.variants.iter().map(|var| format_ident!("{}", var)).collect::<Vec<_>>();
|
||||
let name = format_ident!("{}", name);
|
||||
|
||||
let kinds = if variants.is_empty() { vec![name.clone()] } else { variants.clone() }
|
||||
.into_iter()
|
||||
.map(|name| format_ident!("{}", name.to_string().to_shouty_snake_case()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let variants = if variants.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let kind_enum = format_ident!("{}Kind", name);
|
||||
Some(quote!(
|
||||
pub enum #kind_enum {
|
||||
#(#variants(#variants),)*
|
||||
}
|
||||
|
||||
#(
|
||||
impl From<#variants> for #name {
|
||||
fn from(node: #variants) -> #name {
|
||||
#name { syntax: node.syntax }
|
||||
}
|
||||
}
|
||||
)*
|
||||
|
||||
impl #name {
|
||||
pub fn kind(&self) -> #kind_enum {
|
||||
let syntax = self.syntax.clone();
|
||||
match syntax.kind() {
|
||||
#(
|
||||
#kinds =>
|
||||
#kind_enum::#variants(#variants { syntax }),
|
||||
)*
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
};
|
||||
|
||||
let traits = ast_node.traits.iter().map(|trait_name| {
|
||||
let trait_name = format_ident!("{}", trait_name);
|
||||
quote!(impl ast::#trait_name for #name {})
|
||||
});
|
||||
|
||||
let collections = ast_node.collections.iter().map(|(name, kind)| {
|
||||
let method_name = format_ident!("{}", name);
|
||||
let kind = format_ident!("{}", kind);
|
||||
quote! {
|
||||
pub fn #method_name(&self) -> AstChildren<#kind> {
|
||||
AstChildren::new(&self.syntax)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let options = ast_node.options.iter().map(|attr| {
|
||||
let method_name = match attr {
|
||||
Attr::Type(t) => format_ident!("{}", t.to_snake_case()),
|
||||
Attr::NameType(n, _) => format_ident!("{}", n),
|
||||
};
|
||||
let ty = match attr {
|
||||
Attr::Type(t) | Attr::NameType(_, t) => format_ident!("{}", t),
|
||||
};
|
||||
quote! {
|
||||
pub fn #method_name(&self) -> Option<#ty> {
|
||||
AstChildren::new(&self.syntax).next()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct #name {
|
||||
pub(crate) syntax: SyntaxNode,
|
||||
}
|
||||
|
||||
impl AstNode for #name {
|
||||
fn can_cast(kind: SyntaxKind) -> bool {
|
||||
match kind {
|
||||
#(#kinds)|* => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
fn cast(syntax: SyntaxNode) -> Option<Self> {
|
||||
if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None }
|
||||
}
|
||||
fn syntax(&self) -> &SyntaxNode { &self.syntax }
|
||||
}
|
||||
|
||||
#variants
|
||||
|
||||
#(#traits)*
|
||||
|
||||
impl #name {
|
||||
#(#collections)*
|
||||
#(#options)*
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let ast = quote! {
|
||||
use crate::{
|
||||
SyntaxNode, SyntaxKind::{self, *},
|
||||
ast::{self, AstNode, AstChildren},
|
||||
};
|
||||
|
||||
#(#nodes)*
|
||||
};
|
||||
|
||||
let pretty = reformat(ast)?;
|
||||
Ok(pretty)
|
||||
}
|
||||
|
||||
fn reformat(text: impl std::fmt::Display) -> Result<String> {
|
||||
let mut rustfmt = Command::new("rustfmt")
|
||||
.arg("--config-path")
|
||||
.arg(project_root().join("rustfmt.toml"))
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
write!(rustfmt.stdin.take().unwrap(), "{}", text)?;
|
||||
let output = rustfmt.wait_with_output()?;
|
||||
let stdout = String::from_utf8(output.stdout)?;
|
||||
Ok(stdout)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Grammar {
|
||||
single_byte_tokens: Vec<(String, String)>,
|
||||
|
@ -35,10 +170,14 @@ struct Grammar {
|
|||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct AstNode {
|
||||
#[serde(default)]
|
||||
#[serde(rename = "enum")]
|
||||
variants: Vec<String>,
|
||||
|
||||
#[serde(default)]
|
||||
traits: Vec<String>,
|
||||
#[serde(default)]
|
||||
collections: Vec<Attr>,
|
||||
collections: Vec<(String, String)>,
|
||||
#[serde(default)]
|
||||
options: Vec<Attr>,
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue