git.delta.rocks / jrsonnet / refs/commits / cf33b6edf3e1

difftreelog

source

xtask/src/sourcegen/ast.rs9.0 KiBsourcehistory
1use std::collections::{BTreeSet, HashMap};23use proc_macro2::TokenStream;4use quote::format_ident;5use ungrammar::{Grammar, Rule};67use super::{8	KindsSrc,9	util::{pluralize, to_lower_snake_case},10};1112impl AstNodeSrc {13	pub fn remove_field(&mut self, to_remove: Vec<usize>) {14		to_remove.into_iter().rev().for_each(|idx| {15			self.fields.remove(idx);16		});17	}18}1920#[allow(dead_code)]21#[derive(Default, Debug)]22pub struct AstSrc {23	pub nodes: Vec<AstNodeSrc>,24	pub enums: Vec<AstEnumSrc>,25	pub token_enums: Vec<AstTokenEnumSrc>,26}27#[derive(Debug)]28pub struct AstNodeSrc {29	pub doc: Vec<String>,30	pub name: String,31	pub traits: Vec<String>,32	pub fields: Vec<Field>,33}3435#[derive(Debug, Eq, PartialEq)]36pub enum Field {37	Token(String),38	Node {39		name: String,40		ty: String,41		cardinality: Cardinality,42	},43}4445#[derive(Debug, Eq, PartialEq)]46pub enum Cardinality {47	/// This field may not exist in code48	Optional,49	/// This field should exist in correctly parsed code50	Required,51	/// There may be multiple field values of this kind52	Many,53}5455#[derive(Debug, Clone)]56pub struct AstEnumSrc {57	pub doc: Vec<String>,58	pub name: String,59	pub traits: Vec<String>,60	pub variants: Vec<String>,61}6263#[derive(Debug, Clone)]64pub struct AstTokenEnumSrc {65	#[allow(dead_code)]66	pub doc: Vec<String>,67	pub name: String,68	pub variants: Vec<String>,69}7071impl Field {72	pub fn is_many(&self) -> bool {73		matches!(74			self,75			Self::Node {76				cardinality: Cardinality::Many,77				..78			}79		)80	}8182	pub fn token_name(&self) -> Option<String> {83		match self {84			Self::Token(token) => Some(token.clone()),85			Self::Node { .. } => None,86		}87	}88	pub fn token_kind(&self, kinds: &KindsSrc) -> Option<TokenStream> {89		match self {90			Self::Token(token) => Some(kinds.token(token).expect("token exists").reference()),91			Self::Node { .. } => None,92		}93	}94	pub fn is_token_enum(&self, grammar: &AstSrc) -> bool {95		match self {96			Self::Node { ty, .. } => grammar.token_enums.iter().any(|e| &e.name == ty),97			Self::Token(_) => false,98		}99	}100101	pub fn method_name(&self, kinds: &KindsSrc) -> proc_macro2::Ident {102		match self {103			Self::Token(name) => kinds.token(name).expect("token exists").method_name(),104			Self::Node { name, .. } => {105				format_ident!("{}", name)106			}107		}108	}109	pub fn ty(&self) -> proc_macro2::Ident {110		match self {111			Self::Token(_) => format_ident!("SyntaxToken"),112			Self::Node { ty, .. } => format_ident!("{}", ty),113		}114	}115}116117pub fn lower(kinds: &KindsSrc, grammar: &Grammar) -> AstSrc {118	let mut res = AstSrc::default();119120	let nodes = grammar.iter().collect::<Vec<_>>();121122	for &node in &nodes {123		let name = grammar[node].name.clone();124		let rule = &grammar[node].rule;125		match lower_enum(grammar, rule) {126			Some(variants) => {127				let enum_src = AstEnumSrc {128					doc: Vec::new(),129					name,130					traits: Vec::new(),131					variants,132				};133				res.enums.push(enum_src);134			}135			None => {136				if let Some(variants) = lower_token_enum(grammar, rule) {137					let tokens_enum_src = AstTokenEnumSrc {138						doc: Vec::new(),139						name,140						variants,141					};142					res.token_enums.push(tokens_enum_src);143				} else {144					let mut fields = Vec::new();145					lower_rule(&mut fields, grammar, None, rule, false);146					let mut types = HashMap::new();147					for field in fields.iter().filter(|f| f.token_name().is_none()) {148						if let Some(_old) = types.insert(field.ty(), field.method_name(kinds)) {149							// panic!("{name}.{} has same type as {name}.{}, resolve conflict by wrapping one field: {}", old, field.method_name(kinds), field.ty());150						}151						// TODO: check for assignable field types, i.e you can have152						// ```153						// SomeEnum =154						//     SomeItem155						// |   SomeOtherItem156						// ```157						// And check above will fail to detect conflict in158						// ```159						// SomeStruct =160						//     SomeEnum161						//     SomeItem162						// ```163						// Despite generating getters, which will both return SomeEnum164					}165					res.nodes.push(AstNodeSrc {166						doc: Vec::new(),167						name,168						traits: Vec::new(),169						fields,170					});171				}172			}173		}174	}175176	deduplicate_fields(&mut res);177	extract_struct_traits(kinds, &mut res);178	extract_enum_traits(&mut res);179	res180}181182fn lower_enum(grammar: &Grammar, rule: &Rule) -> Option<Vec<String>> {183	let alternatives = match rule {184		Rule::Alt(it) => it,185		_ => return None,186	};187	let mut variants = Vec::new();188	for alternative in alternatives {189		match alternative {190			Rule::Node(it) => variants.push(grammar[*it].name.clone()),191			Rule::Token(it) if grammar[*it].name == ";" => (),192			_ => return None,193		}194	}195	Some(variants)196}197fn lower_token_enum(grammar: &Grammar, rule: &Rule) -> Option<Vec<String>> {198	let alternatives = match rule {199		Rule::Alt(it) => it,200		_ => return None,201	};202	let mut variants = Vec::new();203	for alternative in alternatives {204		match alternative {205			Rule::Token(it) => variants.push(grammar[*it].name.clone()),206			_ => return None,207		}208	}209	Some(variants)210}211212fn lower_rule(213	acc: &mut Vec<Field>,214	grammar: &Grammar,215	label: Option<&String>,216	rule: &Rule,217	in_optional: bool,218) {219	if lower_comma_list(acc, grammar, label, rule) {220		return;221	}222223	match rule {224		Rule::Node(node) => {225			let ty = grammar[*node].name.clone();226			let name = label.cloned().unwrap_or_else(|| to_lower_snake_case(&ty));227			let field = Field::Node {228				name,229				ty,230				cardinality: if in_optional {231					Cardinality::Optional232				} else {233					Cardinality::Required234				},235			};236			acc.push(field);237		}238		Rule::Token(token) => {239			assert!(label.is_none(), "uexpected label: {label:?}");240			let name = grammar[*token].name.clone();241			let field = Field::Token(name);242			acc.push(field);243		}244		Rule::Rep(inner) => {245			if let Rule::Node(node) = &**inner {246				let ty = grammar[*node].name.clone();247				let name = label248					.cloned()249					.unwrap_or_else(|| pluralize(&to_lower_snake_case(&ty)));250				let field = Field::Node {251					name,252					ty,253					cardinality: Cardinality::Many,254				};255				acc.push(field);256				return;257			}258			todo!("unsupported repitition: {:?}", rule)259		}260		Rule::Labeled { label: l, rule } => {261			assert!(label.is_none());262			lower_rule(acc, grammar, Some(l), rule, in_optional);263		}264		Rule::Seq(rules) | Rule::Alt(rules) => {265			for rule in rules {266				lower_rule(acc, grammar, label, rule, in_optional);267			}268		}269		Rule::Opt(rule) => lower_rule(acc, grammar, label, rule, true),270	}271}272273// (T (',' T)* ','?)274fn lower_comma_list(275	acc: &mut Vec<Field>,276	grammar: &Grammar,277	label: Option<&String>,278	rule: &Rule,279) -> bool {280	let rule = match rule {281		Rule::Seq(it) => it,282		_ => return false,283	};284	let (node, repeat, trailing_comma) = match rule.as_slice() {285		[286			Rule::Node(node),287			Rule::Rep(repeat),288			Rule::Opt(trailing_comma),289		] => (node, repeat, trailing_comma),290		_ => return false,291	};292	let repeat = match &**repeat {293		Rule::Seq(it) => it,294		_ => return false,295	};296	match repeat.as_slice() {297		[comma, Rule::Node(n)] if comma == &**trailing_comma && n == node => (),298		_ => return false,299	}300	let ty = grammar[*node].name.clone();301	let name = label302		.cloned()303		.unwrap_or_else(|| pluralize(&to_lower_snake_case(&ty)));304	let field = Field::Node {305		name,306		ty,307		cardinality: Cardinality::Many,308	};309	acc.push(field);310	true311}312313fn deduplicate_fields(ast: &mut AstSrc) {314	for node in &mut ast.nodes {315		let mut i = 0;316		'outer: while i < node.fields.len() {317			for j in 0..i {318				let f1 = &node.fields[i];319				let f2 = &node.fields[j];320				if f1 == f2 {321					node.fields.remove(i);322					continue 'outer;323				}324			}325			i += 1;326		}327	}328}329330fn extract_struct_traits(kinds: &KindsSrc, ast: &mut AstSrc) {331	// TODO: add common accessor traits here.332	let traits: &[(&str, &[&str])] = &[];333334	for node in &mut ast.nodes {335		for (name, methods) in traits {336			extract_struct_trait(kinds, node, name, methods);337		}338	}339}340341fn extract_struct_trait(342	kinds: &KindsSrc,343	node: &mut AstNodeSrc,344	trait_name: &str,345	methods: &[&str],346) {347	let mut to_remove = Vec::new();348	for (i, field) in node.fields.iter().enumerate() {349		let method_name = field.method_name(kinds).to_string();350		if methods.iter().any(|&it| it == method_name) {351			to_remove.push(i);352		}353	}354	if to_remove.len() == methods.len() {355		node.traits.push(trait_name.to_string());356		node.remove_field(to_remove);357	}358}359360fn extract_enum_traits(ast: &mut AstSrc) {361	let enums = ast.enums.clone();362	for enm in &mut ast.enums {363		let nodes = &ast.nodes;364365		let mut variant_traits = enm.variants.iter().map(|var| {366			nodes367				.iter()368				.find_map(|node| {369					if &node.name != var {370						return None;371					}372					Some(node.traits.iter().cloned().collect::<BTreeSet<_>>())373				})374				.unwrap_or_else(|| {375					enums376						.iter()377						.find_map(|node| {378							if &node.name != var {379								return None;380							}381							Some(node.traits.iter().cloned().collect::<BTreeSet<_>>())382						})383						.unwrap_or_else(|| {384							panic!("could not find struct {var} for enum {}::{var}", enm.name)385						})386				})387		});388389		let mut enum_traits = match variant_traits.next() {390			Some(it) => it,391			None => continue,392		};393		for traits in variant_traits {394			enum_traits = enum_traits.intersection(&traits).cloned().collect();395		}396		enm.traits = enum_traits.into_iter().collect();397	}398}