mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
Merge branch 'main' into redsun82/cargo-upgrade
This commit is contained in:
@@ -46,6 +46,7 @@ codeql_rust_binary(
|
||||
) + [":codegen"],
|
||||
aliases = aliases(),
|
||||
args = ["$(rlocationpath :ungram)"],
|
||||
compile_data = glob(["src/templates/*.mustache"]),
|
||||
data = [":ungram"],
|
||||
proc_macro_deps = all_crate_deps(
|
||||
proc_macro = True,
|
||||
|
||||
@@ -11,3 +11,6 @@ quote = "1.0.38"
|
||||
either = "1.13.0"
|
||||
stdx = {package = "ra_ap_stdx", version = "0.0.257"}
|
||||
itertools = "0.14.0"
|
||||
mustache = "0.9.0"
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
anyhow = "1.0.95"
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use std::io::Write;
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
pub mod codegen;
|
||||
mod flags;
|
||||
use crate::codegen::grammar::ast_src::{AstEnumSrc, Cardinality};
|
||||
use codegen::grammar::ast_src::{AstNodeSrc, AstSrc, Field};
|
||||
use itertools::Itertools;
|
||||
use serde::Serialize;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::env;
|
||||
use ungrammar::Grammar;
|
||||
@@ -56,92 +58,116 @@ fn to_lower_snake_case(s: &str) -> String {
|
||||
buf
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct SchemaField {
|
||||
name: String,
|
||||
ty: String,
|
||||
child: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct SchemaClass {
|
||||
name: String,
|
||||
bases: Vec<String>,
|
||||
fields: Vec<SchemaField>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Default)]
|
||||
struct Schema {
|
||||
classes: Vec<SchemaClass>,
|
||||
}
|
||||
|
||||
fn get_bases(name: &str, super_types: &BTreeMap<String, BTreeSet<String>>) -> Vec<String> {
|
||||
super_types
|
||||
.get(name)
|
||||
.map(|tys| tys.iter().map(|t| class_name(t)).collect())
|
||||
.unwrap_or_else(|| vec!["AstNode".to_string()])
|
||||
}
|
||||
|
||||
fn enum_src_to_schema_class(
|
||||
node: &AstEnumSrc,
|
||||
super_types: &BTreeMap<String, BTreeSet<String>>,
|
||||
) -> SchemaClass {
|
||||
SchemaClass {
|
||||
name: class_name(&node.name),
|
||||
bases: get_bases(&node.name, super_types),
|
||||
fields: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn node_src_to_schema_class(
|
||||
node: &AstNodeSrc,
|
||||
super_types: &BTreeMap<String, BTreeSet<String>>,
|
||||
) -> SchemaClass {
|
||||
SchemaClass {
|
||||
name: class_name(&node.name),
|
||||
bases: get_bases(&node.name, super_types),
|
||||
fields: get_fields(node)
|
||||
.iter()
|
||||
.map(|f| {
|
||||
let (ty, child) = match &f.ty {
|
||||
FieldType::String => ("optional[string]".to_string(), false),
|
||||
FieldType::Predicate => ("predicate".to_string(), false),
|
||||
FieldType::Optional(ty) => (format!("optional[\"{}\"]", class_name(ty)), true),
|
||||
FieldType::List(ty) => (format!("list[\"{}\"]", class_name(ty)), true),
|
||||
};
|
||||
SchemaField {
|
||||
name: property_name(&node.name, &f.name),
|
||||
ty,
|
||||
child,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn fix_blank_lines(s: &str) -> String {
|
||||
// mustache is not very good at avoiding blank lines
|
||||
// adopting the workaround from https://github.com/groue/GRMustache/issues/46#issuecomment-19498046
|
||||
s.split("\n")
|
||||
.filter(|line| !line.trim().is_empty())
|
||||
.map(|line| if line == "¶" { "" } else { line })
|
||||
.join("\n")
|
||||
+ "\n"
|
||||
}
|
||||
|
||||
fn write_schema(
|
||||
grammar: &AstSrc,
|
||||
super_types: BTreeMap<String, BTreeSet<String>>,
|
||||
) -> std::io::Result<String> {
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
writeln!(
|
||||
buf,
|
||||
"# Generated by `ast-generator`, do not edit by hand.\n"
|
||||
)?;
|
||||
writeln!(buf, "from .prelude import *")?;
|
||||
) -> mustache::Result<String> {
|
||||
let mut schema = Schema::default();
|
||||
schema.classes.extend(
|
||||
grammar
|
||||
.enums
|
||||
.iter()
|
||||
.map(|node| enum_src_to_schema_class(node, &super_types)),
|
||||
);
|
||||
schema.classes.extend(
|
||||
grammar
|
||||
.nodes
|
||||
.iter()
|
||||
.map(|node| node_src_to_schema_class(node, &super_types)),
|
||||
);
|
||||
// the concat dance is currently required by bazel
|
||||
let template = mustache::compile_str(include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/src/templates/schema.mustache"
|
||||
)))?;
|
||||
let res = template.render_to_string(&schema)?;
|
||||
Ok(fix_blank_lines(&res))
|
||||
}
|
||||
|
||||
for node in &grammar.enums {
|
||||
let super_classses = if let Some(cls) = super_types.get(&node.name) {
|
||||
let super_classes: Vec<String> = cls.iter().map(|s| class_name(s)).collect();
|
||||
super_classes.join(",")
|
||||
} else {
|
||||
"AstNode".to_owned()
|
||||
};
|
||||
writeln!(
|
||||
buf,
|
||||
"\nclass {}({}):",
|
||||
class_name(&node.name),
|
||||
super_classses
|
||||
)?;
|
||||
writeln!(buf, " pass")?;
|
||||
}
|
||||
for node in &grammar.nodes {
|
||||
let super_classses = if let Some(cls) = super_types.get(&node.name) {
|
||||
let super_classes: Vec<String> = cls.iter().map(|s| class_name(s)).collect();
|
||||
super_classes.join(",")
|
||||
} else {
|
||||
"AstNode".to_owned()
|
||||
};
|
||||
writeln!(
|
||||
buf,
|
||||
"\nclass {}({}):",
|
||||
class_name(&node.name),
|
||||
super_classses
|
||||
)?;
|
||||
let mut empty = true;
|
||||
for field in get_fields(node) {
|
||||
if field.tp == "SyntaxToken" {
|
||||
continue;
|
||||
}
|
||||
|
||||
empty = false;
|
||||
if field.tp == "predicate" {
|
||||
writeln!(
|
||||
buf,
|
||||
" {}: predicate",
|
||||
property_name(&node.name, &field.name),
|
||||
)?;
|
||||
} else if field.tp == "string" {
|
||||
writeln!(
|
||||
buf,
|
||||
" {}: optional[string]",
|
||||
property_name(&node.name, &field.name),
|
||||
)?;
|
||||
} else {
|
||||
let list = field.is_many;
|
||||
let (o, c) = if list {
|
||||
("list[", "]")
|
||||
} else {
|
||||
("optional[", "]")
|
||||
};
|
||||
writeln!(
|
||||
buf,
|
||||
" {}: {}\"{}\"{} | child",
|
||||
property_name(&node.name, &field.name),
|
||||
o,
|
||||
class_name(&field.tp),
|
||||
c
|
||||
)?;
|
||||
};
|
||||
}
|
||||
if empty {
|
||||
writeln!(buf, " pass")?;
|
||||
}
|
||||
}
|
||||
Ok(String::from_utf8_lossy(&buf).to_string())
|
||||
#[derive(Eq, PartialEq)]
|
||||
enum FieldType {
|
||||
String,
|
||||
Predicate,
|
||||
Optional(String),
|
||||
List(String),
|
||||
}
|
||||
|
||||
struct FieldInfo {
|
||||
name: String,
|
||||
tp: String,
|
||||
is_many: bool,
|
||||
ty: FieldType,
|
||||
}
|
||||
fn get_fields(node: &AstNodeSrc) -> Vec<FieldInfo> {
|
||||
let mut result = Vec::new();
|
||||
@@ -154,8 +180,7 @@ fn get_fields(node: &AstNodeSrc) -> Vec<FieldInfo> {
|
||||
if predicates.contains(&name.as_str()) {
|
||||
result.push(FieldInfo {
|
||||
name: format!("is_{name}"),
|
||||
tp: "predicate".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Predicate,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -165,210 +190,177 @@ fn get_fields(node: &AstNodeSrc) -> Vec<FieldInfo> {
|
||||
"Name" | "NameRef" | "Lifetime" => {
|
||||
result.push(FieldInfo {
|
||||
name: "text".to_string(),
|
||||
tp: "string".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::String,
|
||||
});
|
||||
}
|
||||
"Abi" => {
|
||||
result.push(FieldInfo {
|
||||
name: "abi_string".to_string(),
|
||||
tp: "string".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::String,
|
||||
});
|
||||
}
|
||||
"Literal" => {
|
||||
result.push(FieldInfo {
|
||||
name: "text_value".to_string(),
|
||||
tp: "string".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::String,
|
||||
});
|
||||
}
|
||||
"PrefixExpr" => {
|
||||
result.push(FieldInfo {
|
||||
name: "operator_name".to_string(),
|
||||
tp: "string".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::String,
|
||||
});
|
||||
}
|
||||
"BinExpr" => {
|
||||
result.push(FieldInfo {
|
||||
name: "lhs".to_string(),
|
||||
tp: "Expr".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("Expr".to_string()),
|
||||
});
|
||||
result.push(FieldInfo {
|
||||
name: "rhs".to_string(),
|
||||
tp: "Expr".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("Expr".to_string()),
|
||||
});
|
||||
result.push(FieldInfo {
|
||||
name: "operator_name".to_string(),
|
||||
tp: "string".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::String,
|
||||
});
|
||||
}
|
||||
"IfExpr" => {
|
||||
result.push(FieldInfo {
|
||||
name: "then_branch".to_string(),
|
||||
tp: "BlockExpr".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("BlockExpr".to_string()),
|
||||
});
|
||||
result.push(FieldInfo {
|
||||
name: "else_branch".to_string(),
|
||||
tp: "ElseBranch".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("ElseBranch".to_string()),
|
||||
});
|
||||
result.push(FieldInfo {
|
||||
name: "condition".to_string(),
|
||||
tp: "Expr".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("Expr".to_string()),
|
||||
});
|
||||
}
|
||||
"RangeExpr" => {
|
||||
result.push(FieldInfo {
|
||||
name: "start".to_string(),
|
||||
tp: "Expr".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("Expr".to_string()),
|
||||
});
|
||||
result.push(FieldInfo {
|
||||
name: "end".to_string(),
|
||||
tp: "Expr".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("Expr".to_string()),
|
||||
});
|
||||
result.push(FieldInfo {
|
||||
name: "operator_name".to_string(),
|
||||
tp: "string".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::String,
|
||||
});
|
||||
}
|
||||
"RangePat" => {
|
||||
result.push(FieldInfo {
|
||||
name: "start".to_string(),
|
||||
tp: "Pat".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("Pat".to_string()),
|
||||
});
|
||||
result.push(FieldInfo {
|
||||
name: "end".to_string(),
|
||||
tp: "Pat".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("Pat".to_string()),
|
||||
});
|
||||
result.push(FieldInfo {
|
||||
name: "operator_name".to_string(),
|
||||
tp: "string".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::String,
|
||||
});
|
||||
}
|
||||
"IndexExpr" => {
|
||||
result.push(FieldInfo {
|
||||
name: "index".to_string(),
|
||||
tp: "Expr".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("Expr".to_string()),
|
||||
});
|
||||
result.push(FieldInfo {
|
||||
name: "base".to_string(),
|
||||
tp: "Expr".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("Expr".to_string()),
|
||||
});
|
||||
}
|
||||
"Impl" => {
|
||||
result.push(FieldInfo {
|
||||
name: "trait_".to_string(),
|
||||
tp: "Type".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("Type".to_string()),
|
||||
});
|
||||
result.push(FieldInfo {
|
||||
name: "self_ty".to_string(),
|
||||
tp: "Type".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("Type".to_string()),
|
||||
});
|
||||
}
|
||||
"ForExpr" => {
|
||||
result.push(FieldInfo {
|
||||
name: "iterable".to_string(),
|
||||
tp: "Expr".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("Expr".to_string()),
|
||||
});
|
||||
}
|
||||
"WhileExpr" => {
|
||||
result.push(FieldInfo {
|
||||
name: "condition".to_string(),
|
||||
tp: "Expr".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("Expr".to_string()),
|
||||
});
|
||||
}
|
||||
"MatchGuard" => {
|
||||
result.push(FieldInfo {
|
||||
name: "condition".to_string(),
|
||||
tp: "Expr".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("Expr".to_string()),
|
||||
});
|
||||
}
|
||||
"MacroDef" => {
|
||||
result.push(FieldInfo {
|
||||
name: "args".to_string(),
|
||||
tp: "TokenTree".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("TokenTree".to_string()),
|
||||
});
|
||||
result.push(FieldInfo {
|
||||
name: "body".to_string(),
|
||||
tp: "TokenTree".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("TokenTree".to_string()),
|
||||
});
|
||||
}
|
||||
"FormatArgsExpr" => {
|
||||
result.push(FieldInfo {
|
||||
name: "args".to_string(),
|
||||
tp: "FormatArgsArg".to_string(),
|
||||
is_many: true,
|
||||
ty: FieldType::List("FormatArgsArg".to_string()),
|
||||
});
|
||||
}
|
||||
"ArgList" => {
|
||||
result.push(FieldInfo {
|
||||
name: "args".to_string(),
|
||||
tp: "Expr".to_string(),
|
||||
is_many: true,
|
||||
ty: FieldType::List("Expr".to_string()),
|
||||
});
|
||||
}
|
||||
"Fn" => {
|
||||
result.push(FieldInfo {
|
||||
name: "body".to_string(),
|
||||
tp: "BlockExpr".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("BlockExpr".to_string()),
|
||||
});
|
||||
}
|
||||
"Const" => {
|
||||
result.push(FieldInfo {
|
||||
name: "body".to_string(),
|
||||
tp: "Expr".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("Expr".to_string()),
|
||||
});
|
||||
}
|
||||
"Static" => {
|
||||
result.push(FieldInfo {
|
||||
name: "body".to_string(),
|
||||
tp: "Expr".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("Expr".to_string()),
|
||||
});
|
||||
}
|
||||
"ClosureExpr" => {
|
||||
result.push(FieldInfo {
|
||||
name: "body".to_string(),
|
||||
tp: "Expr".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("Expr".to_string()),
|
||||
});
|
||||
}
|
||||
"ArrayExpr" => {
|
||||
result.push(FieldInfo {
|
||||
name: "is_semicolon".to_string(),
|
||||
tp: "predicate".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Predicate,
|
||||
});
|
||||
}
|
||||
"SelfParam" => {
|
||||
result.push(FieldInfo {
|
||||
name: "is_amp".to_string(),
|
||||
tp: "predicate".to_string(),
|
||||
is_many: false,
|
||||
ty: FieldType::Predicate,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
@@ -379,72 +371,69 @@ fn get_fields(node: &AstNodeSrc) -> Vec<FieldInfo> {
|
||||
if node.name == "ArrayExpr" && field.method_name() == "expr" {
|
||||
continue;
|
||||
}
|
||||
let ty = match field {
|
||||
Field::Token(_) => continue,
|
||||
Field::Node {
|
||||
ty, cardinality, ..
|
||||
} => match cardinality {
|
||||
Cardinality::Optional => FieldType::Optional(ty.clone()),
|
||||
Cardinality::Many => FieldType::List(ty.clone()),
|
||||
},
|
||||
};
|
||||
result.push(FieldInfo {
|
||||
name: field.method_name(),
|
||||
tp: field.ty().to_string(),
|
||||
is_many: field.is_many(),
|
||||
ty,
|
||||
});
|
||||
}
|
||||
for trait_ in &node.traits {
|
||||
match trait_.as_str() {
|
||||
"HasAttrs" => result.push(FieldInfo {
|
||||
name: "attrs".to_owned(),
|
||||
tp: "Attr".to_owned(),
|
||||
is_many: true,
|
||||
ty: FieldType::List("Attr".to_owned()),
|
||||
}),
|
||||
"HasName" => result.push(FieldInfo {
|
||||
name: "name".to_owned(),
|
||||
tp: "Name".to_owned(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("Name".to_owned()),
|
||||
}),
|
||||
"HasVisibility" => result.push(FieldInfo {
|
||||
name: "visibility".to_owned(),
|
||||
tp: "Visibility".to_owned(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("Visibility".to_owned()),
|
||||
}),
|
||||
"HasGenericParams" => {
|
||||
result.push(FieldInfo {
|
||||
name: "generic_param_list".to_owned(),
|
||||
tp: "GenericParamList".to_owned(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("GenericParamList".to_owned()),
|
||||
});
|
||||
result.push(FieldInfo {
|
||||
name: "where_clause".to_owned(),
|
||||
tp: "WhereClause".to_owned(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("WhereClause".to_owned()),
|
||||
})
|
||||
}
|
||||
"HasGenericArgs" => result.push(FieldInfo {
|
||||
name: "generic_arg_list".to_owned(),
|
||||
tp: "GenericArgList".to_owned(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("GenericArgList".to_owned()),
|
||||
}),
|
||||
"HasTypeBounds" => result.push(FieldInfo {
|
||||
name: "type_bound_list".to_owned(),
|
||||
tp: "TypeBoundList".to_owned(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("TypeBoundList".to_owned()),
|
||||
}),
|
||||
"HasModuleItem" => result.push(FieldInfo {
|
||||
name: "items".to_owned(),
|
||||
tp: "Item".to_owned(),
|
||||
is_many: true,
|
||||
ty: FieldType::List("Item".to_owned()),
|
||||
}),
|
||||
"HasLoopBody" => {
|
||||
result.push(FieldInfo {
|
||||
name: "label".to_owned(),
|
||||
tp: "Label".to_owned(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("Label".to_owned()),
|
||||
});
|
||||
result.push(FieldInfo {
|
||||
name: "loop_body".to_owned(),
|
||||
tp: "BlockExpr".to_owned(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("BlockExpr".to_owned()),
|
||||
})
|
||||
}
|
||||
"HasArgList" => result.push(FieldInfo {
|
||||
name: "arg_list".to_owned(),
|
||||
tp: "ArgList".to_owned(),
|
||||
is_many: false,
|
||||
ty: FieldType::Optional("ArgList".to_owned()),
|
||||
}),
|
||||
"HasDocComments" => {}
|
||||
|
||||
@@ -455,141 +444,122 @@ fn get_fields(node: &AstNodeSrc) -> Vec<FieldInfo> {
|
||||
result
|
||||
}
|
||||
|
||||
fn write_extractor(grammar: &AstSrc) -> std::io::Result<String> {
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
writeln!(
|
||||
buf,
|
||||
"//! Generated by `ast-generator`, do not edit by hand.\n
|
||||
#![cfg_attr(any(), rustfmt::skip)]
|
||||
|
||||
use super::base::Translator;
|
||||
use super::mappings::TextValue;
|
||||
use crate::emit_detached;
|
||||
use crate::generated;
|
||||
use crate::trap::{{Label, TrapId}};
|
||||
use ra_ap_syntax::ast::{{
|
||||
HasArgList, HasAttrs, HasGenericArgs, HasGenericParams, HasLoopBody, HasModuleItem, HasName,
|
||||
HasTypeBounds, HasVisibility, RangeItem,
|
||||
}};
|
||||
use ra_ap_syntax::{{ast, AstNode}};
|
||||
|
||||
impl Translator<'_> {{
|
||||
fn emit_else_branch(&mut self, node: ast::ElseBranch) -> Label<generated::Expr> {{
|
||||
match node {{
|
||||
ast::ElseBranch::IfExpr(inner) => self.emit_if_expr(inner).into(),
|
||||
ast::ElseBranch::Block(inner) => self.emit_block_expr(inner).into(),
|
||||
}}
|
||||
}}\n"
|
||||
)?;
|
||||
for node in &grammar.enums {
|
||||
let type_name = &node.name;
|
||||
let class_name = class_name(&node.name);
|
||||
|
||||
writeln!(
|
||||
buf,
|
||||
" pub(crate) fn emit_{}(&mut self, node: ast::{}) -> Label<generated::{}> {{",
|
||||
to_lower_snake_case(type_name),
|
||||
type_name,
|
||||
class_name
|
||||
)?;
|
||||
writeln!(buf, " match node {{")?;
|
||||
for variant in &node.variants {
|
||||
writeln!(
|
||||
buf,
|
||||
" ast::{}::{}(inner) => self.emit_{}(inner).into(),",
|
||||
type_name,
|
||||
variant,
|
||||
to_lower_snake_case(variant)
|
||||
)?;
|
||||
}
|
||||
writeln!(buf, " }}")?;
|
||||
writeln!(buf, " }}\n")?;
|
||||
}
|
||||
|
||||
for node in &grammar.nodes {
|
||||
let type_name = &node.name;
|
||||
let class_name = class_name(&node.name);
|
||||
|
||||
writeln!(
|
||||
buf,
|
||||
" pub(crate) fn emit_{}(&mut self, node: ast::{}) -> Label<generated::{}> {{",
|
||||
to_lower_snake_case(type_name),
|
||||
type_name,
|
||||
class_name
|
||||
)?;
|
||||
for field in get_fields(node) {
|
||||
if &field.tp == "SyntaxToken" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let type_name = &field.tp;
|
||||
let struct_field_name = &field.name;
|
||||
let class_field_name = property_name(&node.name, &field.name);
|
||||
if field.tp == "predicate" {
|
||||
writeln!(
|
||||
buf,
|
||||
" let {} = node.{}_token().is_some();",
|
||||
class_field_name,
|
||||
&struct_field_name[3..],
|
||||
)?;
|
||||
} else if field.tp == "string" {
|
||||
writeln!(
|
||||
buf,
|
||||
" let {} = node.try_get_text();",
|
||||
class_field_name,
|
||||
)?;
|
||||
} else if field.is_many {
|
||||
writeln!(
|
||||
buf,
|
||||
" let {} = node.{}().map(|x| self.emit_{}(x)).collect();",
|
||||
class_field_name,
|
||||
struct_field_name,
|
||||
to_lower_snake_case(type_name)
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
buf,
|
||||
" let {} = node.{}().map(|x| self.emit_{}(x));",
|
||||
class_field_name,
|
||||
struct_field_name,
|
||||
to_lower_snake_case(type_name)
|
||||
)?;
|
||||
}
|
||||
}
|
||||
writeln!(
|
||||
buf,
|
||||
" let label = self.trap.emit(generated::{} {{",
|
||||
class_name
|
||||
)?;
|
||||
writeln!(buf, " id: TrapId::Star,")?;
|
||||
for field in get_fields(node) {
|
||||
if field.tp == "SyntaxToken" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let class_field_name: String = property_name(&node.name, &field.name);
|
||||
writeln!(buf, " {},", class_field_name)?;
|
||||
}
|
||||
writeln!(buf, " }});")?;
|
||||
writeln!(buf, " self.emit_location(label, &node);")?;
|
||||
writeln!(
|
||||
buf,
|
||||
" emit_detached!({}, self, node, label);",
|
||||
class_name
|
||||
)?;
|
||||
writeln!(
|
||||
buf,
|
||||
" self.emit_tokens(&node, label.into(), node.syntax().children_with_tokens());"
|
||||
)?;
|
||||
writeln!(buf, " label")?;
|
||||
|
||||
writeln!(buf, " }}\n")?;
|
||||
}
|
||||
writeln!(buf, "}}")?;
|
||||
Ok(String::from_utf8_lossy(&buf).into_owned())
|
||||
#[derive(Serialize)]
|
||||
struct EnumVariantInfo {
|
||||
name: String,
|
||||
snake_case_name: String,
|
||||
}
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
#[derive(Serialize)]
|
||||
struct ExtractorEnumInfo {
|
||||
name: String,
|
||||
snake_case_name: String,
|
||||
ast_name: String,
|
||||
variants: Vec<EnumVariantInfo>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Default)]
|
||||
struct ExtractorNodeFieldInfo {
|
||||
name: String,
|
||||
method: String,
|
||||
snake_case_ty: String,
|
||||
string: bool,
|
||||
predicate: bool,
|
||||
optional: bool,
|
||||
list: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ExtractorNodeInfo {
|
||||
name: String,
|
||||
snake_case_name: String,
|
||||
ast_name: String,
|
||||
fields: Vec<ExtractorNodeFieldInfo>,
|
||||
has_attrs: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ExtractorInfo {
|
||||
enums: Vec<ExtractorEnumInfo>,
|
||||
nodes: Vec<ExtractorNodeInfo>,
|
||||
}
|
||||
|
||||
fn enum_to_extractor_info(node: &AstEnumSrc) -> ExtractorEnumInfo {
|
||||
ExtractorEnumInfo {
|
||||
name: class_name(&node.name),
|
||||
snake_case_name: to_lower_snake_case(&node.name),
|
||||
ast_name: node.name.clone(),
|
||||
variants: node
|
||||
.variants
|
||||
.iter()
|
||||
.map(|v| EnumVariantInfo {
|
||||
name: v.clone(),
|
||||
snake_case_name: to_lower_snake_case(v),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn field_info_to_extractor_info(node: &AstNodeSrc, field: &FieldInfo) -> ExtractorNodeFieldInfo {
|
||||
let name = property_name(&node.name, &field.name);
|
||||
match &field.ty {
|
||||
FieldType::String => ExtractorNodeFieldInfo {
|
||||
name,
|
||||
string: true,
|
||||
..Default::default()
|
||||
},
|
||||
FieldType::Predicate => ExtractorNodeFieldInfo {
|
||||
name,
|
||||
method: format!("{}_token", &field.name[3..]),
|
||||
predicate: true,
|
||||
..Default::default()
|
||||
},
|
||||
FieldType::Optional(ty) => ExtractorNodeFieldInfo {
|
||||
name,
|
||||
method: field.name.clone(),
|
||||
snake_case_ty: to_lower_snake_case(ty),
|
||||
optional: true,
|
||||
..Default::default()
|
||||
},
|
||||
FieldType::List(ty) => ExtractorNodeFieldInfo {
|
||||
name,
|
||||
method: field.name.clone(),
|
||||
snake_case_ty: to_lower_snake_case(ty),
|
||||
list: true,
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
fn node_to_extractor_info(node: &AstNodeSrc) -> ExtractorNodeInfo {
|
||||
let fields = get_fields(node);
|
||||
let has_attrs = fields.iter().any(|f| f.name == "attrs");
|
||||
ExtractorNodeInfo {
|
||||
name: class_name(&node.name),
|
||||
snake_case_name: to_lower_snake_case(&node.name),
|
||||
ast_name: node.name.clone(),
|
||||
fields: fields
|
||||
.iter()
|
||||
.map(|f| field_info_to_extractor_info(node, f))
|
||||
.collect(),
|
||||
has_attrs,
|
||||
}
|
||||
}
|
||||
|
||||
fn write_extractor(grammar: &AstSrc) -> mustache::Result<String> {
|
||||
let extractor_info = ExtractorInfo {
|
||||
enums: grammar.enums.iter().map(enum_to_extractor_info).collect(),
|
||||
nodes: grammar.nodes.iter().map(node_to_extractor_info).collect(),
|
||||
};
|
||||
// the concat dance is currently required by bazel
|
||||
let template = mustache::compile_str(include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/src/templates/extractor.mustache"
|
||||
)))?;
|
||||
let res = template.render_to_string(&extractor_info)?;
|
||||
Ok(fix_blank_lines(&res))
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let grammar = PathBuf::from("..").join(env::args().nth(1).expect("grammar file path required"));
|
||||
let grammar: Grammar = fs::read_to_string(&grammar)
|
||||
.unwrap_or_else(|_| panic!("Failed to parse grammar file: {}", grammar.display()))
|
||||
|
||||
65
rust/ast-generator/src/templates/extractor.mustache
Normal file
65
rust/ast-generator/src/templates/extractor.mustache
Normal file
@@ -0,0 +1,65 @@
|
||||
//! Generated by `ast-generator`, do not edit by hand.
|
||||
¶{{! <- denotes empty line that should be kept, all blank lines are removed otherwise}}
|
||||
#![cfg_attr(any(), rustfmt::skip)]
|
||||
¶
|
||||
use super::base::Translator;
|
||||
use super::mappings::TextValue;
|
||||
use crate::emit_detached;
|
||||
use crate::generated;
|
||||
use crate::trap::{Label, TrapId};
|
||||
use ra_ap_syntax::ast::{
|
||||
HasArgList, HasAttrs, HasGenericArgs, HasGenericParams, HasLoopBody, HasModuleItem, HasName,
|
||||
HasTypeBounds, HasVisibility, RangeItem,
|
||||
};
|
||||
use ra_ap_syntax::{ast, AstNode};
|
||||
¶
|
||||
impl Translator<'_> {
|
||||
fn emit_else_branch(&mut self, node: ast::ElseBranch) -> Option<Label<generated::Expr>> {
|
||||
match node {
|
||||
ast::ElseBranch::IfExpr(inner) => self.emit_if_expr(inner).map(Into::into),
|
||||
ast::ElseBranch::Block(inner) => self.emit_block_expr(inner).map(Into::into),
|
||||
}
|
||||
}
|
||||
{{#enums}}
|
||||
¶
|
||||
pub(crate) fn emit_{{snake_case_name}}(&mut self, node: ast::{{ast_name}}) -> Option<Label<generated::{{name}}>> {
|
||||
match node {
|
||||
{{#variants}}
|
||||
ast::{{ast_name}}::{{name}}(inner) => self.emit_{{snake_case_name}}(inner).map(Into::into),
|
||||
{{/variants}}
|
||||
}
|
||||
}
|
||||
{{/enums}}
|
||||
{{#nodes}}
|
||||
¶
|
||||
pub(crate) fn emit_{{snake_case_name}}(&mut self, node: ast::{{ast_name}}) -> Option<Label<generated::{{name}}>> {
|
||||
{{#has_attrs}}
|
||||
if self.should_be_excluded(&node) { return None; }
|
||||
{{/has_attrs}}
|
||||
{{#fields}}
|
||||
{{#predicate}}
|
||||
let {{name}} = node.{{method}}().is_some();
|
||||
{{/predicate}}
|
||||
{{#string}}
|
||||
let {{name}} = node.try_get_text();
|
||||
{{/string}}
|
||||
{{#list}}
|
||||
let {{name}} = node.{{method}}().filter_map(|x| self.emit_{{snake_case_ty}}(x)).collect();
|
||||
{{/list}}
|
||||
{{#optional}}
|
||||
let {{name}} = node.{{method}}().and_then(|x| self.emit_{{snake_case_ty}}(x));
|
||||
{{/optional}}
|
||||
{{/fields}}
|
||||
let label = self.trap.emit(generated::{{name}} {
|
||||
id: TrapId::Star,
|
||||
{{#fields}}
|
||||
{{name}},
|
||||
{{/fields}}
|
||||
});
|
||||
self.emit_location(label, &node);
|
||||
emit_detached!({{name}}, self, node, label);
|
||||
self.emit_tokens(&node, label.into(), node.syntax().children_with_tokens());
|
||||
Some(label)
|
||||
}
|
||||
{{/nodes}}
|
||||
}
|
||||
13
rust/ast-generator/src/templates/schema.mustache
Normal file
13
rust/ast-generator/src/templates/schema.mustache
Normal file
@@ -0,0 +1,13 @@
|
||||
# Generated by `ast-generator`, do not edit by hand.
|
||||
¶{{! <- denotes empty line that should be kept, all blank lines are removed otherwise}}
|
||||
from .prelude import *
|
||||
{{#classes}}
|
||||
¶
|
||||
class {{name}}({{#bases}}{{.}}, {{/bases}}):
|
||||
{{#fields}}
|
||||
{{name}}: {{{ty}}}{{#child}} | child{{/child}}
|
||||
{{/fields}}
|
||||
{{^fields}}
|
||||
pass
|
||||
{{/fields}}
|
||||
{{/classes}}
|
||||
Reference in New Issue
Block a user