git.delta.rocks / jrsonnet / refs/commits / 8f8545cd220d

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	util::{pluralize, to_lower_snake_case},9	KindsSrc,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	pub doc: Vec<String>,66	pub name: String,67	pub variants: Vec<String>,68}6970impl Field {71	pub fn is_many(&self) -> bool {72		matches!(73			self,74			Field::Node {75				cardinality: Cardinality::Many,76				..77			}78		)79	}8081	pub fn token_name(&self) -> Option<String> {82		match self {83			Field::Token(token) => Some(token.clone()),84			_ => None,85		}86	}87	pub fn token_kind(&self, kinds: &KindsSrc) -> Option<TokenStream> {88		match self {89			Field::Token(token) => Some(kinds.token(token).expect("token exists").reference()),90			_ => None,91		}92	}93	pub fn is_token_enum(&self, grammar: &AstSrc) -> bool {94		match self {95			Field::Node { ty, .. } => grammar.token_enums.iter().any(|e| &e.name == ty),96			_ => false,97		}98	}99100	pub fn method_name(&self, kinds: &KindsSrc) -> proc_macro2::Ident {101		match self {102			Field::Token(name) => kinds.token(name).expect("token exists").method_name(),103			Field::Node { name, .. } => {104				format_ident!("{}", name)105			}106		}107	}108	pub fn ty(&self) -> proc_macro2::Ident {109		match self {110			Field::Token(_) => format_ident!("SyntaxToken"),111			Field::Node { ty, .. } => format_ident!("{}", ty),112		}113	}114}115116pub fn lower(kinds: &KindsSrc, grammar: &Grammar) -> AstSrc {117	let mut res = AstSrc {118		// tokens,119		..Default::default()120	};121122	let nodes = grammar.iter().collect::<Vec<_>>();123124	for &node in &nodes {125		let name = grammar[node].name.clone();126		let rule = &grammar[node].rule;127		match lower_enum(grammar, rule) {128			Some(variants) => {129				let enum_src = AstEnumSrc {130					doc: Vec::new(),131					name,132					traits: Vec::new(),133					variants,134				};135				res.enums.push(enum_src);136			}137			None => match lower_token_enum(grammar, rule) {138				Some(variants) => {139					let tokens_enum_src = AstTokenEnumSrc {140						doc: Vec::new(),141						name,142						variants,143					};144					res.token_enums.push(tokens_enum_src);145				}146				None => {147					let mut fields = Vec::new();148					lower_rule(&mut fields, grammar, None, rule, false);149					let mut types = HashMap::new();150					for field in fields.iter().filter(|f| f.token_name().is_none()) {151						if let Some(_old) = types.insert(field.ty(), field.method_name(kinds)) {152							// panic!("{name}.{} has same type as {name}.{}, resolve conflict by wrapping one field: {}", old, field.method_name(kinds), field.ty());153						}154						// TODO: check for assignable field types, i.e you can have155						// ```156						// SomeEnum =157						//     SomeItem158						// |   SomeOtherItem159						// ```160						// And check above will fail to detect conflict in161						// ```162						// SomeStruct =163						//     SomeEnum164						//     SomeItem165						// ```166						// Despite generating getters, which will both return SomeEnum167					}168					res.nodes.push(AstNodeSrc {169						doc: Vec::new(),170						name,171						traits: Vec::new(),172						fields,173					});174				}175			},176		}177	}178179	deduplicate_fields(&mut res);180	extract_struct_traits(kinds, &mut res);181	extract_enum_traits(&mut res);182	res183}184185fn lower_enum(grammar: &Grammar, rule: &Rule) -> Option<Vec<String>> {186	let alternatives = match rule {187		Rule::Alt(it) => it,188		_ => return None,189	};190	let mut variants = Vec::new();191	for alternative in alternatives {192		match alternative {193			Rule::Node(it) => variants.push(grammar[*it].name.clone()),194			Rule::Token(it) if grammar[*it].name == ";" => (),195			_ => return None,196		}197	}198	Some(variants)199}200fn lower_token_enum(grammar: &Grammar, rule: &Rule) -> Option<Vec<String>> {201	let alternatives = match rule {202		Rule::Alt(it) => it,203		_ => return None,204	};205	let mut variants = Vec::new();206	for alternative in alternatives {207		match alternative {208			Rule::Token(it) => variants.push(grammar[*it].name.clone()),209			_ => return None,210		}211	}212	Some(variants)213}214215fn lower_rule(216	acc: &mut Vec<Field>,217	grammar: &Grammar,218	label: Option<&String>,219	rule: &Rule,220	in_optional: bool,221) {222	if lower_comma_list(acc, grammar, label, rule) {223		return;224	}225226	match rule {227		Rule::Node(node) => {228			let ty = grammar[*node].name.clone();229			let name = label.cloned().unwrap_or_else(|| to_lower_snake_case(&ty));230			let field = Field::Node {231				name,232				ty,233				cardinality: if in_optional {234					Cardinality::Optional235				} else {236					Cardinality::Required237				},238			};239			acc.push(field);240		}241		Rule::Token(token) => {242			assert!(label.is_none(), "uexpected label: {:?}", label);243			let name = grammar[*token].name.clone();244			let field = Field::Token(name);245			acc.push(field);246		}247		Rule::Rep(inner) => {248			if let Rule::Node(node) = &**inner {249				let ty = grammar[*node].name.clone();250				let name = label251					.cloned()252					.unwrap_or_else(|| pluralize(&to_lower_snake_case(&ty)));253				let field = Field::Node {254					name,255					ty,256					cardinality: Cardinality::Many,257				};258				acc.push(field);259				return;260			}261			todo!("unsupported repitition: {:?}", rule)262		}263		Rule::Labeled { label: l, rule } => {264			assert!(label.is_none());265			lower_rule(acc, grammar, Some(l), rule, in_optional);266		}267		Rule::Seq(rules) | Rule::Alt(rules) => {268			for rule in rules {269				lower_rule(acc, grammar, label, rule, in_optional)270			}271		}272		Rule::Opt(rule) => lower_rule(acc, grammar, label, rule, true),273	}274}275276// (T (',' T)* ','?)277fn lower_comma_list(278	acc: &mut Vec<Field>,279	grammar: &Grammar,280	label: Option<&String>,281	rule: &Rule,282) -> bool {283	let rule = match rule {284		Rule::Seq(it) => it,285		_ => return false,286	};287	let (node, repeat, trailing_comma) = match rule.as_slice() {288		[Rule::Node(node), Rule::Rep(repeat), Rule::Opt(trailing_comma)] => {289			(node, repeat, trailing_comma)290		}291		_ => return false,292	};293	let repeat = match &**repeat {294		Rule::Seq(it) => it,295		_ => return false,296	};297	match repeat.as_slice() {298		[comma, Rule::Node(n)] if comma == &**trailing_comma && n == node => (),299		_ => return false,300	}301	let ty = grammar[*node].name.clone();302	let name = label303		.cloned()304		.unwrap_or_else(|| pluralize(&to_lower_snake_case(&ty)));305	let field = Field::Node {306		name,307		ty,308		cardinality: Cardinality::Many,309	};310	acc.push(field);311	true312}313314fn deduplicate_fields(ast: &mut AstSrc) {315	for node in &mut ast.nodes {316		let mut i = 0;317		'outer: while i < node.fields.len() {318			for j in 0..i {319				let f1 = &node.fields[i];320				let f2 = &node.fields[j];321				if f1 == f2 {322					node.fields.remove(i);323					continue 'outer;324				}325			}326			i += 1;327		}328	}329}330331fn extract_struct_traits(kinds: &KindsSrc, ast: &mut AstSrc) {332	// TODO: add common accessor traits here.333	let traits: &[(&str, &[&str])] = &[];334335	for node in &mut ast.nodes {336		for (name, methods) in traits {337			extract_struct_trait(kinds, node, name, methods);338		}339	}340}341342fn extract_struct_trait(343	kinds: &KindsSrc,344	node: &mut AstNodeSrc,345	trait_name: &str,346	methods: &[&str],347) {348	let mut to_remove = Vec::new();349	for (i, field) in node.fields.iter().enumerate() {350		let method_name = field.method_name(kinds).to_string();351		if methods.iter().any(|&it| it == method_name) {352			to_remove.push(i);353		}354	}355	if to_remove.len() == methods.len() {356		node.traits.push(trait_name.to_string());357		node.remove_field(to_remove);358	}359}360361fn extract_enum_traits(ast: &mut AstSrc) {362	let enums = ast.enums.clone();363	for enm in &mut ast.enums {364		let nodes = &ast.nodes;365366		let mut variant_traits = enm.variants.iter().map(|var| {367			nodes368				.iter()369				.find_map(|node| {370					if &node.name != var {371						return None;372					}373					Some(node.traits.iter().cloned().collect::<BTreeSet<_>>())374				})375				.unwrap_or_else(|| {376					enums377						.iter()378						.find_map(|node| {379							if &node.name != var {380								return None;381							}382							Some(node.traits.iter().cloned().collect::<BTreeSet<_>>())383						})384						.unwrap_or_else(|| {385							panic!("could not find struct {var} for enum {}::{var}", enm.name)386						})387				})388		});389390		let mut enum_traits = match variant_traits.next() {391			Some(it) => it,392			None => continue,393		};394		for traits in variant_traits {395			enum_traits = enum_traits.intersection(&traits).cloned().collect();396		}397		enm.traits = enum_traits.into_iter().collect();398	}399}