1use std::fs;
2use std::path::PathBuf;
3
4use genco::prelude::*;
5use xshell::Shell;
6
7use crate::cairo_spec::get_spec;
8use crate::spec::{Member, Node, NodeKind, Variant, Variants};
9
10pub fn project_root() -> PathBuf {
11 let dir = env!("CARGO_MANIFEST_DIR");
13 let res = PathBuf::from(dir).parent().unwrap().parent().unwrap().to_owned();
15 assert!(res.join("Cargo.toml").exists(), "Could not find project root directory.");
16 res
17}
18
19pub fn ensure_file_content(filename: PathBuf, content: String) {
20 if let Ok(old_contents) = fs::read_to_string(&filename)
21 && old_contents == content
22 {
23 return;
24 }
25
26 fs::write(&filename, content).unwrap();
27}
28
29pub fn get_codes() -> Vec<(String, String)> {
30 vec![
31 (
32 "crates/cairo-lang-syntax/src/node/ast.rs".into(),
33 reformat_rust_code(generate_ast_code().to_string().unwrap()),
34 ),
35 (
36 "crates/cairo-lang-syntax/src/node/kind.rs".into(),
37 reformat_rust_code(generate_kinds_code().to_string().unwrap()),
38 ),
39 (
40 "crates/cairo-lang-syntax/src/node/key_fields.rs".into(),
41 reformat_rust_code(generate_key_fields_code().to_string().unwrap()),
42 ),
43 ]
44}
45
46pub fn reformat_rust_code(text: String) -> String {
47 reformat_rust_code_inner(reformat_rust_code_inner(text))
49}
50pub fn reformat_rust_code_inner(text: String) -> String {
51 let sh = Shell::new().unwrap();
52 let cmd = sh.cmd("rustfmt").env("RUSTUP_TOOLCHAIN", "nightly-2026-05-17");
53 let cmd_with_args = cmd.arg("--config-path").arg(project_root().join("rustfmt.toml"));
54 let mut stdout = cmd_with_args.stdin(text).read().unwrap();
55 if !stdout.ends_with('\n') {
56 stdout.push('\n');
57 }
58 stdout
59}
60
61fn generate_kinds_code() -> rust::Tokens {
62 let spec = get_spec();
63 let mut tokens = quote! {
64 $("// Autogenerated file. To regenerate, please run `cargo run --bin generate-syntax`.")
65 use core::fmt;
66 use serde::{Deserialize, Serialize};
67 };
68
69 let kinds = name_tokens(&spec, |k| !matches!(k, NodeKind::Enum { .. }));
71 let token_kinds = name_tokens(&spec, |k| matches!(k, NodeKind::Token { .. }));
72 let keyword_token_kinds =
73 name_tokens(&spec, |k| matches!(k, NodeKind::Token { is_keyword } if *is_keyword));
74 let terminal_kinds = name_tokens(&spec, |k| matches!(k, NodeKind::Terminal { .. }));
75 let keyword_terminal_kinds =
76 name_tokens(&spec, |k| matches!(k, NodeKind::Terminal { is_keyword, .. } if *is_keyword));
77 let missing_kinds = spec.iter().filter_map(|n| match &n.kind {
78 NodeKind::Missing => Some(n.name.as_str()),
79 NodeKind::Token { .. } if n.name == "TokenMissing" => Some(n.name.as_str()),
80 _ => None,
81 });
82
83 tokens.extend(quote! {
84 #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, salsa::Update, cairo_lang_proc_macros::HeapSize)]
85 pub enum SyntaxKind {
86 $(for t in kinds => $t,)
87 }
88 impl SyntaxKind {
89 pub fn is_token(&self) -> bool {
90 matches!(
91 *self,
92 $(for t in token_kinds join ( | ) => SyntaxKind::$t)
93 )
94 }
95 pub fn is_terminal(&self) -> bool {
96 matches!(
97 *self,
98 $(for t in terminal_kinds join ( | ) => SyntaxKind::$t)
99 )
100 }
101 pub fn is_keyword_token(&self) -> bool {
102 matches!(
103 *self,
104 $(for t in keyword_token_kinds join ( | ) => SyntaxKind::$t)
105 )
106 }
107 pub fn is_keyword_terminal(&self) -> bool {
108 matches!(
109 *self,
110 $(for t in keyword_terminal_kinds join ( | ) => SyntaxKind::$t)
111 )
112 }
113 pub fn is_missing(&self) -> bool {
114 matches!(
115 *self,
116 $(for t in missing_kinds join ( | ) => SyntaxKind::$t)
117 )
118 }
119 }
120 impl fmt::Display for SyntaxKind {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 write!(f, "{self:?}")
123 }
124 }
125 });
126 tokens
127}
128
129fn name_tokens(spec: &[Node], predicate: impl Fn(&NodeKind) -> bool) -> impl Iterator<Item = &str> {
131 spec.iter().filter(move |n| predicate(&n.kind)).map(|n| n.name.as_str())
132}
133
134fn generate_key_fields_code() -> rust::Tokens {
135 let spec = get_spec();
136 let mut arms = rust::Tokens::new();
137
138 for Node { name, kind } in spec {
139 match kind {
140 NodeKind::Struct { members } | NodeKind::Terminal { members, .. } => {
141 let mut fields = rust::Tokens::new();
142 let mut key_fields_range = 0..0;
143 for (i, member) in members.into_iter().enumerate() {
144 let field_name = member.name;
145 if member.key {
146 if key_fields_range.is_empty() {
147 key_fields_range = i..(i + 1);
148 } else {
149 assert_eq!(key_fields_range.end, i, "Key fields must be contiguous.");
150 key_fields_range.end = i + 1;
151 }
152 if !fields.is_empty() {
153 fields.extend(quote! { $(", ") });
154 }
155 fields.extend(quote!($field_name));
156 }
157 }
158 if !fields.is_empty() {
159 arms.extend(quote! {
160 $("\n// Key fields:") $fields.$("\n")
161 });
162 }
163 let key_fields_range =
164 format!("{}..{}", key_fields_range.start, key_fields_range.end);
165 arms.extend(quote! {
166 SyntaxKind::$name => $key_fields_range,
167 });
168 }
169 NodeKind::List { .. }
170 | NodeKind::SeparatedList { .. }
171 | NodeKind::Token { .. }
172 | NodeKind::Missing => {
173 arms.extend(quote! {
174 SyntaxKind::$name => 0..0,
175 });
176 }
177 NodeKind::Enum { .. } => {}
178 }
179 }
180 let tokens = quote! {
181 $("// Autogenerated file. To regenerate, please run `cargo run --bin generate-syntax`.")
182 use super::kind::SyntaxKind;
183 $("/// Gets the vector of children ids that are the indexing key for this SyntaxKind.")
184 $("///")
185 $("/// Each SyntaxKind has some children that are defined in the spec to be its indexing key")
186 $("/// for its stable pointer. See [super::stable_ptr].")
187 pub fn key_fields_range(kind: SyntaxKind) -> core::ops::Range<usize> {
188 match kind {
189 $arms
190 }
191 }
192 };
193 tokens
194}
195
196fn generate_ast_code() -> rust::Tokens {
197 let spec = get_spec();
198 let mut tokens = quote! {
199 $("// Autogenerated file. To regenerate, please run `cargo run --bin generate-syntax`.")
200 #![allow(clippy::match_single_binding)]
201 #![allow(clippy::too_many_arguments)]
202 #![allow(dead_code)]
203 #![allow(unused_variables)]
204 use std::ops::Deref;
205
206 use cairo_lang_filesystem::span::TextWidth;
207 use cairo_lang_filesystem::ids::SmolStrId;
208 use cairo_lang_utils::{extract_matches, Intern};
209 use cairo_lang_proc_macros::HeapSize;
210
211 use salsa::Database;
212
213 use super::element_list::ElementList;
214 use super::green::GreenNodeDetails;
215 use super::kind::SyntaxKind;
216 use super::{
217 GreenId, GreenNode, SyntaxNode, SyntaxStablePtrId, Terminal, Token, TypedStablePtr,
218 TypedSyntaxNode,
219 };
220 #[path = "ast_ext.rs"]
221 mod ast_ext;
222 };
223 let all_token_variants: Vec<_> = spec
224 .iter()
225 .filter(|node| matches!(node.kind, NodeKind::Terminal { .. }))
226 .map(|node| Variant { name: node.name.clone(), kind: node.name.clone() })
227 .collect();
228 for Node { name, kind } in spec {
229 tokens.extend(match kind {
230 NodeKind::Enum { variants, missing_variant } => {
231 let variants_list = match variants {
232 Variants::List(variants) => variants,
233 Variants::AllTokens => all_token_variants.clone(),
234 };
235 gen_enum_code(name, variants_list, missing_variant)
236 }
237 NodeKind::Struct { members } => gen_struct_code(name, members, false),
238 NodeKind::Missing => gen_struct_code(name, vec![], false),
239 NodeKind::Terminal { members, .. } => gen_struct_code(name, members, true),
240 NodeKind::Token { .. } => gen_token_code(name),
241 NodeKind::List { element_type } => gen_list_code(name, element_type),
242 NodeKind::SeparatedList { element_type, separator_type } => {
243 gen_separated_list_code(name, element_type, separator_type)
244 }
245 });
246 }
247 tokens
248}
249
250fn gen_list_code(name: String, element_type: String) -> rust::Tokens {
251 let ptr_name = format!("{name}Ptr");
253 let green_name = format!("{name}Green");
254 let element_green_name = format!("{element_type}Green");
255 let common_code = gen_common_list_code(&name, &green_name, &ptr_name);
256 quote! {
257 #[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
258 pub struct $(&name)<'db>(ElementList<'db, $(&element_type)<'db>, 1>);
259 impl<'db> Deref for $(&name)<'db>{
260 type Target = ElementList<'db, $(&element_type)<'db>, 1>;
261 fn deref(&self) -> &Self::Target {
262 &self.0
263 }
264 }
265 impl<'db> $(&name)<'db>{
266 pub fn new_green(
267 db: &'db dyn Database, children: &[$(&element_green_name)<'db>]
268 ) -> $(&green_name)<'db> {
269 let width = children.iter().map(|id|
270 id.0.long(db).width(db)).sum();
271 $(&green_name)(GreenNode {
272 kind: SyntaxKind::$(&name),
273 details: GreenNodeDetails::Node {
274 children: children.iter().map(|x| x.0).collect(),
275 width,
276 },
277 }.intern(db))
278 }
279 }
280 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update, HeapSize)]
281 pub struct $(&ptr_name)<'db>(pub SyntaxStablePtrId<'db>);
282 impl<'db> TypedStablePtr<'db> for $(&ptr_name)<'db> {
283 type SyntaxNode = $(&name)<'db>;
284 fn untyped(self) -> SyntaxStablePtrId<'db> {
285 self.0
286 }
287 fn lookup(&self, db: &'db dyn Database) -> $(&name)<'db> {
288 $(&name)::from_syntax_node(db, self.0.lookup(db))
289 }
290 }
291 impl<'db> From<$(&ptr_name)<'db>> for SyntaxStablePtrId<'db> {
292 fn from(ptr: $(&ptr_name)<'db>) -> Self {
293 ptr.untyped()
294 }
295 }
296 $common_code
297 }
298}
299
300fn gen_separated_list_code(
301 name: String,
302 element_type: String,
303 separator_type: String,
304) -> rust::Tokens {
305 let ptr_name = format!("{name}Ptr");
307 let green_name = format!("{name}Green");
308 let element_or_separator_green_name = format!("{name}ElementOrSeparatorGreen");
309 let element_green_name = format!("{element_type}Green");
310 let separator_green_name = format!("{separator_type}Green");
311 let common_code = gen_common_list_code(&name, &green_name, &ptr_name);
312 quote! {
313 #[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
314 pub struct $(&name)<'db>(ElementList<'db, $(&element_type)<'db>, 2>);
315 impl<'db> Deref for $(&name)<'db>{
316 type Target = ElementList<'db, $(&element_type)<'db>, 2>;
317 fn deref(&self) -> &Self::Target {
318 &self.0
319 }
320 }
321 impl<'db> $(&name)<'db>{
322 pub fn new_green(
323 db: &'db dyn Database, children: &[$(&element_or_separator_green_name)<'db>]
324 ) -> $(&green_name)<'db> {
325 let width = children.iter().map(|id|
326 id.id().long(db).width(db)).sum();
327 $(&green_name)(GreenNode {
328 kind: SyntaxKind::$(&name),
329 details: GreenNodeDetails::Node {
330 children: children.iter().map(|x| x.id()).collect(),
331 width,
332 },
333 }.intern(db))
334 }
335 }
336 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update, HeapSize)]
337 pub struct $(&ptr_name)<'db>(pub SyntaxStablePtrId<'db>);
338 impl<'db> TypedStablePtr<'db> for $(&ptr_name)<'db> {
339 type SyntaxNode = $(&name)<'db>;
340 fn untyped(self) -> SyntaxStablePtrId<'db> {
341 self.0
342 }
343 fn lookup(&self, db: &'db dyn Database) -> $(&name)<'db> {
344 $(&name)::from_syntax_node(db, self.0.lookup(db))
345 }
346 }
347 impl<'db> From<$(&ptr_name)<'db>> for SyntaxStablePtrId<'db> {
348 fn from(ptr: $(&ptr_name)<'db>) -> Self {
349 ptr.untyped()
350 }
351 }
352 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update)]
353 pub enum $(&element_or_separator_green_name)<'db> {
354 Separator($(&separator_green_name)<'db>),
355 Element($(&element_green_name)<'db>),
356 }
357 impl<'db> From<$(&separator_green_name)<'db>> for $(&element_or_separator_green_name)<'db> {
358 fn from(value: $(&separator_green_name)<'db>) -> Self {
359 $(&element_or_separator_green_name)::Separator(value)
360 }
361 }
362 impl<'db> From<$(&element_green_name)<'db>> for $(&element_or_separator_green_name)<'db> {
363 fn from(value: $(&element_green_name)<'db>) -> Self {
364 $(&element_or_separator_green_name)::Element(value)
365 }
366 }
367 impl<'db> $(&element_or_separator_green_name)<'db> {
368 fn id(&self) -> GreenId<'db> {
369 match self {
370 $(&element_or_separator_green_name)::Separator(green) => green.0,
371 $(&element_or_separator_green_name)::Element(green) => green.0,
372 }
373 }
374 }
375 $common_code
376 }
377}
378
379fn gen_common_list_code(name: &str, green_name: &str, ptr_name: &str) -> rust::Tokens {
380 quote! {
381 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update)]
382 pub struct $green_name<'db>(pub GreenId<'db>);
383 impl<'db> TypedSyntaxNode<'db> for $name<'db> {
384 const OPTIONAL_KIND: Option<SyntaxKind> = Some(SyntaxKind::$name);
385 type StablePtr = $ptr_name<'db>;
386 type Green = $green_name<'db>;
387 fn missing(db: &'db dyn Database) -> Self::Green {
388 $green_name(
389 GreenNode {
390 kind: SyntaxKind::$name,
391 details: GreenNodeDetails::Node { children: [].into(), width: TextWidth::default() },
392 }.intern(db)
393 )
394 }
395 fn from_syntax_node(db: &'db dyn Database, node: SyntaxNode<'db>) -> Self {
396 Self(ElementList::new(node))
397 }
398 fn cast(db: &'db dyn Database, node: SyntaxNode<'db>) -> Option<Self> {
399 if node.kind(db) == SyntaxKind::$name {
400 Some(Self(ElementList::new(node)))
401 } else {
402 None
403 }
404 }
405 fn as_syntax_node(&self) -> SyntaxNode<'db> {
406 self.node
407 }
408 fn stable_ptr(&self, db: &'db dyn Database) -> Self::StablePtr {
409 $ptr_name(self.node.stable_ptr(db))
410 }
411 }
412 }
413}
414
415fn gen_enum_code(name: String, variants: Vec<Variant>, missing_variant: bool) -> rust::Tokens {
416 let ptr_name = format!("{name}Ptr");
417 let green_name = format!("{name}Green");
418 let mut enum_body = quote! {};
419 let mut from_node_body = quote! {};
420 let mut cast_body = quote! {};
421 let mut ptr_conversions = quote! {};
422 let mut green_conversions = quote! {};
423 for variant in &variants {
424 let n = &variant.name;
425 let k = &variant.kind;
426
427 enum_body.extend(quote! {
428 $n($k<'db>),
429 });
430 from_node_body.extend(quote! {
431 SyntaxKind::$k => $(&name)::$n($k::from_syntax_node(db, node)),
432 });
433 cast_body.extend(quote! {
434 SyntaxKind::$k => Some($(&name)::$n($k::from_syntax_node(db, node))),
435 });
436 let variant_ptr = format!("{k}Ptr");
437 ptr_conversions.extend(quote! {
438 impl<'db> From<$(&variant_ptr)<'db>> for $(&ptr_name)<'db> {
439 fn from(value: $(&variant_ptr)<'db>) -> Self {
440 Self(value.0)
441 }
442 }
443 });
444 let variant_green = format!("{k}Green");
445 green_conversions.extend(quote! {
446 impl<'db> From<$(&variant_green)<'db>> for $(&green_name)<'db> {
447 fn from(value: $(&variant_green)<'db>) -> Self {
448 Self(value.0)
449 }
450 }
451 });
452 }
453 let missing_body = if missing_variant {
454 let missing_kind = format!("{name}Missing");
455 quote! {
456 $(&green_name)($(missing_kind)::missing(db).0)
457 }
458 } else {
459 quote! {
460 panic!("No missing variant.");
461 }
462 };
463 quote! {
464 #[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
465 pub enum $(&name)<'db>{
466 $enum_body
467 }
468 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update, HeapSize)]
469 pub struct $(&ptr_name)<'db>(pub SyntaxStablePtrId<'db>);
470 impl<'db> TypedStablePtr<'db> for $(&ptr_name)<'db> {
471 type SyntaxNode = $(&name)<'db>;
472 fn untyped(self) -> SyntaxStablePtrId<'db> {
473 self.0
474 }
475 fn lookup(&self, db: &'db dyn Database) -> Self::SyntaxNode {
476 $(&name)::from_syntax_node(db, self.0.lookup(db))
477 }
478 }
479 impl<'db> From<$(&ptr_name)<'db>> for SyntaxStablePtrId<'db> {
480 fn from(ptr: $(&ptr_name)<'db>) -> Self {
481 ptr.untyped()
482 }
483 }
484 $ptr_conversions
485 $green_conversions
486 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update)]
487 pub struct $(&green_name)<'db>(pub GreenId<'db>);
488 impl<'db> TypedSyntaxNode<'db> for $(&name)<'db>{
489 const OPTIONAL_KIND: Option<SyntaxKind> = None;
490 type StablePtr = $(&ptr_name)<'db>;
491 type Green = $(&green_name)<'db>;
492 fn missing(db: &'db dyn Database) -> Self::Green {
493 $missing_body
494 }
495 fn from_syntax_node(db: &'db dyn Database, node: SyntaxNode<'db>) -> Self {
496 let kind = node.kind(db);
497 match kind{
498 $from_node_body
499 _ => panic!(
500 "Unexpected syntax kind {:?} when constructing {}.",
501 kind,
502 $[str]($[const](&name))),
503 }
504 }
505 fn cast(db: &'db dyn Database, node: SyntaxNode<'db>) -> Option<Self> {
506 let kind = node.kind(db);
507 match kind {
508 $cast_body
509 _ => None,
510 }
511 }
512 fn as_syntax_node(&self) -> SyntaxNode<'db> {
513 match self {
514 $(for v in &variants => $(&name)::$(&v.name)(x) => x.as_syntax_node(),)
515 }
516 }
517 fn stable_ptr(&self, db: &'db dyn Database) -> Self::StablePtr {
518 $(&ptr_name)(self.as_syntax_node().stable_ptr(db))
519 }
520 }
521 impl<'db> $(&name)<'db> {
522 $("/// Checks if a kind of a variant of [")$(&name)$("].")
523 pub fn is_variant(kind: SyntaxKind) -> bool {
524 matches!(kind, $(for v in &variants join (|) => SyntaxKind::$(&v.kind)))
525 }
526 }
527 }
528}
529
530fn gen_token_code(name: String) -> rust::Tokens {
531 let green_name = format!("{name}Green");
532 let ptr_name = format!("{name}Ptr");
533
534 quote! {
535 #[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
536 pub struct $(&name)<'db> {
537 node: SyntaxNode<'db>,
538 }
539 impl<'db> Token<'db> for $(&name)<'db> {
540 fn new_green(db: &'db dyn Database, text: SmolStrId<'db>) -> Self::Green {
541 $(&green_name)(GreenNode {
542 kind: SyntaxKind::$(&name),
543 details: GreenNodeDetails::Token(text),
544 }.intern(db))
545 }
546 fn text(&self, db: &'db dyn Database) -> SmolStrId<'db> {
547 *extract_matches!(&self.node.green_node(db).details,
548 GreenNodeDetails::Token)
549 }
550 }
551 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update, HeapSize)]
552 pub struct $(&ptr_name)<'db>(pub SyntaxStablePtrId<'db>);
553 impl<'db> TypedStablePtr<'db> for $(&ptr_name)<'db> {
554 type SyntaxNode = $(&name)<'db>;
555 fn untyped(self) -> SyntaxStablePtrId<'db> {
556 self.0
557 }
558 fn lookup(&self, db: &'db dyn Database) -> $(&name)<'db> {
559 $(&name)::from_syntax_node(db, self.0.lookup(db))
560 }
561 }
562 impl<'db> From<$(&ptr_name)<'db>> for SyntaxStablePtrId<'db> {
563 fn from(ptr: $(&ptr_name)<'db>) -> Self {
564 ptr.untyped()
565 }
566 }
567 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update)]
568 pub struct $(&green_name)<'db>(pub GreenId<'db>);
569 impl<'db> $(&green_name)<'db> {
570 pub fn text(&self, db: &'db dyn Database) -> SmolStrId<'db> {
571 *extract_matches!(&self.0.long(db).details, GreenNodeDetails::Token)
572 }
573 }
574 impl<'db> TypedSyntaxNode<'db> for $(&name)<'db>{
575 const OPTIONAL_KIND: Option<SyntaxKind> = Some(SyntaxKind::$(&name));
576 type StablePtr = $(&ptr_name)<'db>;
577 type Green = $(&green_name)<'db>;
578 fn missing(db: &'db dyn Database) -> Self::Green {
579 $(&green_name)(GreenNode {
580 kind: SyntaxKind::TokenMissing,
581 details: GreenNodeDetails::Token(SmolStrId::from(db, "")),
582 }.intern(db))
583 }
584 fn from_syntax_node(db: &'db dyn Database, node: SyntaxNode<'db>) -> Self {
585 match node.green_node(db).details {
586 GreenNodeDetails::Token(_) => Self { node },
587 GreenNodeDetails::Node { .. } => panic!(
588 "Expected a token {:?}, not an internal node",
589 SyntaxKind::$(&name)
590 ),
591 }
592 }
593 fn cast(db: &'db dyn Database, node: SyntaxNode<'db>) -> Option<Self> {
594 match node.green_node(db).details {
595 GreenNodeDetails::Token(_) => Some(Self { node }),
596 GreenNodeDetails::Node { .. } => None,
597 }
598 }
599 fn as_syntax_node(&self) -> SyntaxNode<'db> {
600 self.node
601 }
602 fn stable_ptr(&self, db: &'db dyn Database) -> Self::StablePtr {
603 $(&ptr_name)(self.node.stable_ptr(db))
604 }
605 }
606 }
607}
608
609fn gen_struct_code(name: String, members: Vec<Member>, is_terminal: bool) -> rust::Tokens {
610 let green_name = format!("{name}Green");
611 let mut body = rust::Tokens::new();
612 let mut field_indices = quote! {};
613 let mut args = quote! {};
614 let mut params = quote! {};
615 let mut args_for_missing = quote! {};
616 let mut ptr_getters = quote! {};
617 let mut key_field_index: usize = 0;
618 for (i, Member { name, kind, key }) in members.iter().enumerate() {
619 let index_name = format!("INDEX_{}", name.to_uppercase());
620 field_indices.extend(quote! {
621 pub const $index_name : usize = $i;
622 });
623 let key_name_green = format!("{name}_green");
624 args.extend(quote! {$name.0,});
625 let child_green = format!("{kind}Green");
628 params.extend(quote! {$name: $(&child_green)<'db>,});
629 body.extend(quote! {
630 pub fn $name(&self, db: &'db dyn Database) -> $kind<'db> {
631 $kind::from_syntax_node(db, self.node.get_children(db)[$i])
632 }
633 });
634 args_for_missing.extend(quote! {$kind::missing(db).0,});
635
636 if *key {
637 ptr_getters.extend(quote! {
638 pub fn $(&key_name_green)(self, db: &'db dyn Database) -> $(&child_green)<'db> {
639 $(&child_green)(self.0.0.key_fields(db)[$key_field_index])
640 }
641 });
642 key_field_index += 1;
643 }
644 }
645 let ptr_name = format!("{name}Ptr");
646 let new_green_impl = if is_terminal {
647 let token_name = name.replace("Terminal", "Token");
648 quote! {
649 impl<'db> Terminal<'db> for $(&name)<'db> {
650 const KIND: SyntaxKind = SyntaxKind::$(&name);
651 type TokenType = $(&token_name)<'db>;
652 fn new_green(
653 db: &'db dyn Database,
654 leading_trivia: TriviaGreen<'db>,
655 token: <<$(&name)<'db> as Terminal<'db>>::TokenType as TypedSyntaxNode<'db>>::Green,
656 trailing_trivia: TriviaGreen<'db>
657 ) -> Self::Green {
658 let children = [$args];
659 let width =
660 children.into_iter().map(|id: GreenId<'_>| id.long(db).width(db)).sum();
661 $(&green_name)(GreenNode {
662 kind: SyntaxKind::$(&name),
663 details: GreenNodeDetails::Node { children: children.into(), width },
664 }.intern(db))
665 }
666 fn text(&self, db: &'db dyn Database) -> SmolStrId<'db> {
667 let GreenNodeDetails::Node{children,..} = &self.node.green_node(db).details else {
668 unreachable!("Expected a node, not a token");
669 };
670 *extract_matches!(&children[1].long(db).details, GreenNodeDetails::Token)
671 }
672 }
673 }
674 } else {
675 quote! {
676 impl<'db> $(&name)<'db> {
677 $field_indices
678 pub fn new_green(db: &'db dyn Database, $params) -> $(&green_name)<'db> {
679 let children = [$args];
680 let width =
681 children.into_iter().map(|id: GreenId<'_>| id.long(db).width(db)).sum();
682 $(&green_name)(GreenNode {
683 kind: SyntaxKind::$(&name),
684 details: GreenNodeDetails::Node { children: children.into(), width },
685 }.intern(db))
686 }
687 }
688 }
689 };
690 quote! {
691 #[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
692 pub struct $(&name)<'db> {
693 node: SyntaxNode<'db>,
694 }
695 $new_green_impl
696 impl<'db> $(&name)<'db> {
697 $body
698 }
699 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update, HeapSize)]
700 pub struct $(&ptr_name)<'db>(pub SyntaxStablePtrId<'db>);
701 impl<'db> $(&ptr_name)<'db> {
702 $ptr_getters
703 }
704 impl<'db> TypedStablePtr<'db> for $(&ptr_name)<'db> {
705 type SyntaxNode = $(&name)<'db>;
706 fn untyped(self) -> SyntaxStablePtrId<'db> {
707 self.0
708 }
709 fn lookup(&self, db: &'db dyn Database) -> $(&name)<'db> {
710 $(&name)::from_syntax_node(db, self.0.lookup(db))
711 }
712 }
713 impl<'db> From<$(&ptr_name)<'db>> for SyntaxStablePtrId<'db> {
714 fn from(ptr: $(&ptr_name)<'db>) -> Self {
715 ptr.untyped()
716 }
717 }
718 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update)]
719 pub struct $(&green_name)<'db>(pub GreenId<'db>);
720 impl<'db> TypedSyntaxNode<'db> for $(&name)<'db> {
721 const OPTIONAL_KIND: Option<SyntaxKind> = Some(SyntaxKind::$(&name));
722 type StablePtr = $(&ptr_name)<'db>;
723 type Green = $(&green_name)<'db>;
724 fn missing(db: &'db dyn Database) -> Self::Green {
725 $(&green_name)(GreenNode {
728 kind: SyntaxKind::$(&name),
729 details: GreenNodeDetails::Node {
730 children: [$args_for_missing].into(),
731 width: TextWidth::default(),
732 },
733 }.intern(db))
734 }
735 fn from_syntax_node(db: &'db dyn Database, node: SyntaxNode<'db>) -> Self {
736 let kind = node.kind(db);
737 assert_eq!(kind, SyntaxKind::$(&name), "Unexpected SyntaxKind {:?}. Expected {:?}.", kind, SyntaxKind::$(&name));
738 Self { node }
739 }
740 fn cast(db: &'db dyn Database, node: SyntaxNode<'db>) -> Option<Self> {
741 let kind = node.kind(db);
742 if kind == SyntaxKind::$(&name) {
743 Some(Self::from_syntax_node(db, node))
744 } else {
745 None
746 }
747 }
748 fn as_syntax_node(&self) -> SyntaxNode<'db> {
749 self.node
750 }
751 fn stable_ptr(&self, db: &'db dyn Database) -> Self::StablePtr {
752 $(&ptr_name)(self.node.stable_ptr(db))
753 }
754 }
755 }
756}