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::Enum { missing_variant, .. } => missing_variant.as_ref().map(|v| v.kind.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 { .. } | NodeKind::SeparatedList { .. } | NodeKind::Token { .. } => {
170 arms.extend(quote! {
171 SyntaxKind::$name => 0..0,
172 });
173 }
174 NodeKind::Enum { .. } => {}
175 }
176 }
177 let tokens = quote! {
178 $("// Autogenerated file. To regenerate, please run `cargo run --bin generate-syntax`.")
179 use super::kind::SyntaxKind;
180 $("/// Gets the vector of children ids that are the indexing key for this SyntaxKind.")
181 $("///")
182 $("/// Each SyntaxKind has some children that are defined in the spec to be its indexing key")
183 $("/// for its stable pointer. See [super::stable_ptr].")
184 pub fn key_fields_range(kind: SyntaxKind) -> core::ops::Range<usize> {
185 match kind {
186 $arms
187 }
188 }
189 };
190 tokens
191}
192
193fn generate_ast_code() -> rust::Tokens {
194 let spec = get_spec();
195 let mut tokens = quote! {
196 $("// Autogenerated file. To regenerate, please run `cargo run --bin generate-syntax`.")
197 #![allow(clippy::match_single_binding)]
198 #![allow(clippy::too_many_arguments)]
199 #![allow(dead_code)]
200 #![allow(unused_variables)]
201 use std::ops::Deref;
202
203 use cairo_lang_filesystem::span::TextWidth;
204 use cairo_lang_filesystem::ids::SmolStrId;
205 use cairo_lang_utils::{extract_matches, Intern};
206 use cairo_lang_proc_macros::HeapSize;
207
208 use salsa::Database;
209
210 use super::element_list::ElementList;
211 use super::green::GreenNodeDetails;
212 use super::kind::SyntaxKind;
213 use super::{
214 GreenId, GreenNode, SyntaxNode, SyntaxStablePtrId, Terminal, Token, TypedStablePtr,
215 TypedSyntaxNode,
216 };
217 #[path = "ast_ext.rs"]
218 mod ast_ext;
219 };
220 let all_token_variants: Vec<_> = spec
221 .iter()
222 .filter(|node| matches!(node.kind, NodeKind::Terminal { .. }))
223 .map(|node| Variant { name: node.name.clone(), kind: node.name.clone() })
224 .collect();
225 for Node { name, kind } in spec {
226 tokens.extend(match kind {
227 NodeKind::Enum { variants, missing_variant } => {
228 let variants_list = match variants {
229 Variants::List(variants) => variants,
230 Variants::AllTokens => all_token_variants.clone(),
231 };
232 gen_enum_code(name, variants_list, missing_variant)
233 }
234 NodeKind::Struct { members } => gen_struct_code(name, members, false),
235 NodeKind::Terminal { members, .. } => gen_struct_code(name, members, true),
236 NodeKind::Token { .. } => gen_token_code(name),
237 NodeKind::List { element_type } => gen_list_code(name, element_type),
238 NodeKind::SeparatedList { element_type, separator_type } => {
239 gen_separated_list_code(name, element_type, separator_type)
240 }
241 });
242 }
243 tokens
244}
245
246fn gen_list_code(name: String, element_type: String) -> rust::Tokens {
247 let ptr_name = format!("{name}Ptr");
249 let green_name = format!("{name}Green");
250 let element_green_name = format!("{element_type}Green");
251 let common_code = gen_common_list_code(&name, &green_name, &ptr_name);
252 quote! {
253 #[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
254 pub struct $(&name)<'db>(ElementList<'db, $(&element_type)<'db>, 1>);
255 impl<'db> Deref for $(&name)<'db>{
256 type Target = ElementList<'db, $(&element_type)<'db>, 1>;
257 fn deref(&self) -> &Self::Target {
258 &self.0
259 }
260 }
261 impl<'db> $(&name)<'db>{
262 pub fn new_green(
263 db: &'db dyn Database, children: &[$(&element_green_name)<'db>]
264 ) -> $(&green_name)<'db> {
265 let width = children.iter().map(|id|
266 id.0.long(db).width(db)).sum();
267 $(&green_name)(GreenNode {
268 kind: SyntaxKind::$(&name),
269 details: GreenNodeDetails::Node {
270 children: children.iter().map(|x| x.0).collect(),
271 width,
272 },
273 }.intern(db))
274 }
275 }
276 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update, HeapSize)]
277 pub struct $(&ptr_name)<'db>(pub SyntaxStablePtrId<'db>);
278 impl<'db> TypedStablePtr<'db> for $(&ptr_name)<'db> {
279 type SyntaxNode = $(&name)<'db>;
280 fn untyped(self) -> SyntaxStablePtrId<'db> {
281 self.0
282 }
283 fn lookup(&self, db: &'db dyn Database) -> $(&name)<'db> {
284 $(&name)::from_syntax_node(db, self.0.lookup(db))
285 }
286 }
287 impl<'db> From<$(&ptr_name)<'db>> for SyntaxStablePtrId<'db> {
288 fn from(ptr: $(&ptr_name)<'db>) -> Self {
289 ptr.untyped()
290 }
291 }
292 $common_code
293 }
294}
295
296fn gen_separated_list_code(
297 name: String,
298 element_type: String,
299 separator_type: String,
300) -> rust::Tokens {
301 let ptr_name = format!("{name}Ptr");
303 let green_name = format!("{name}Green");
304 let element_or_separator_green_name = format!("{name}ElementOrSeparatorGreen");
305 let element_green_name = format!("{element_type}Green");
306 let separator_green_name = format!("{separator_type}Green");
307 let common_code = gen_common_list_code(&name, &green_name, &ptr_name);
308 quote! {
309 #[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
310 pub struct $(&name)<'db>(ElementList<'db, $(&element_type)<'db>, 2>);
311 impl<'db> Deref for $(&name)<'db>{
312 type Target = ElementList<'db, $(&element_type)<'db>, 2>;
313 fn deref(&self) -> &Self::Target {
314 &self.0
315 }
316 }
317 impl<'db> $(&name)<'db>{
318 pub fn new_green(
319 db: &'db dyn Database, children: &[$(&element_or_separator_green_name)<'db>]
320 ) -> $(&green_name)<'db> {
321 let width = children.iter().map(|id|
322 id.id().long(db).width(db)).sum();
323 $(&green_name)(GreenNode {
324 kind: SyntaxKind::$(&name),
325 details: GreenNodeDetails::Node {
326 children: children.iter().map(|x| x.id()).collect(),
327 width,
328 },
329 }.intern(db))
330 }
331 }
332 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update, HeapSize)]
333 pub struct $(&ptr_name)<'db>(pub SyntaxStablePtrId<'db>);
334 impl<'db> TypedStablePtr<'db> for $(&ptr_name)<'db> {
335 type SyntaxNode = $(&name)<'db>;
336 fn untyped(self) -> SyntaxStablePtrId<'db> {
337 self.0
338 }
339 fn lookup(&self, db: &'db dyn Database) -> $(&name)<'db> {
340 $(&name)::from_syntax_node(db, self.0.lookup(db))
341 }
342 }
343 impl<'db> From<$(&ptr_name)<'db>> for SyntaxStablePtrId<'db> {
344 fn from(ptr: $(&ptr_name)<'db>) -> Self {
345 ptr.untyped()
346 }
347 }
348 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update)]
349 pub enum $(&element_or_separator_green_name)<'db> {
350 Separator($(&separator_green_name)<'db>),
351 Element($(&element_green_name)<'db>),
352 }
353 impl<'db> From<$(&separator_green_name)<'db>> for $(&element_or_separator_green_name)<'db> {
354 fn from(value: $(&separator_green_name)<'db>) -> Self {
355 $(&element_or_separator_green_name)::Separator(value)
356 }
357 }
358 impl<'db> From<$(&element_green_name)<'db>> for $(&element_or_separator_green_name)<'db> {
359 fn from(value: $(&element_green_name)<'db>) -> Self {
360 $(&element_or_separator_green_name)::Element(value)
361 }
362 }
363 impl<'db> $(&element_or_separator_green_name)<'db> {
364 fn id(&self) -> GreenId<'db> {
365 match self {
366 $(&element_or_separator_green_name)::Separator(green) => green.0,
367 $(&element_or_separator_green_name)::Element(green) => green.0,
368 }
369 }
370 }
371 $common_code
372 }
373}
374
375fn gen_common_list_code(name: &str, green_name: &str, ptr_name: &str) -> rust::Tokens {
376 quote! {
377 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update)]
378 pub struct $green_name<'db>(pub GreenId<'db>);
379 impl<'db> TypedSyntaxNode<'db> for $name<'db> {
380 const OPTIONAL_KIND: Option<SyntaxKind> = Some(SyntaxKind::$name);
381 type StablePtr = $ptr_name<'db>;
382 type Green = $green_name<'db>;
383 fn missing(db: &'db dyn Database) -> Self::Green {
384 $green_name(
385 GreenNode {
386 kind: SyntaxKind::$name,
387 details: GreenNodeDetails::Node { children: [].into(), width: TextWidth::default() },
388 }.intern(db)
389 )
390 }
391 fn from_syntax_node(db: &'db dyn Database, node: SyntaxNode<'db>) -> Self {
392 Self(ElementList::new(node))
393 }
394 fn cast(db: &'db dyn Database, node: SyntaxNode<'db>) -> Option<Self> {
395 if node.kind(db) == SyntaxKind::$name {
396 Some(Self(ElementList::new(node)))
397 } else {
398 None
399 }
400 }
401 fn as_syntax_node(&self) -> SyntaxNode<'db> {
402 self.node
403 }
404 fn stable_ptr(&self, db: &'db dyn Database) -> Self::StablePtr {
405 $ptr_name(self.node.stable_ptr(db))
406 }
407 }
408 }
409}
410
411fn gen_enum_code(
412 name: String,
413 variants: Vec<Variant>,
414 missing_variant: Option<Variant>,
415) -> 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 = match missing_variant {
454 Some(missing) => quote! {
455 $(&green_name)($(missing.kind)::missing(db).0)
456 },
457 None => quote! {
458 panic!("No missing variant.");
459 },
460 };
461 quote! {
462 #[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
463 pub enum $(&name)<'db>{
464 $enum_body
465 }
466 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update, HeapSize)]
467 pub struct $(&ptr_name)<'db>(pub SyntaxStablePtrId<'db>);
468 impl<'db> TypedStablePtr<'db> for $(&ptr_name)<'db> {
469 type SyntaxNode = $(&name)<'db>;
470 fn untyped(self) -> SyntaxStablePtrId<'db> {
471 self.0
472 }
473 fn lookup(&self, db: &'db dyn Database) -> Self::SyntaxNode {
474 $(&name)::from_syntax_node(db, self.0.lookup(db))
475 }
476 }
477 impl<'db> From<$(&ptr_name)<'db>> for SyntaxStablePtrId<'db> {
478 fn from(ptr: $(&ptr_name)<'db>) -> Self {
479 ptr.untyped()
480 }
481 }
482 $ptr_conversions
483 $green_conversions
484 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update)]
485 pub struct $(&green_name)<'db>(pub GreenId<'db>);
486 impl<'db> TypedSyntaxNode<'db> for $(&name)<'db>{
487 const OPTIONAL_KIND: Option<SyntaxKind> = None;
488 type StablePtr = $(&ptr_name)<'db>;
489 type Green = $(&green_name)<'db>;
490 fn missing(db: &'db dyn Database) -> Self::Green {
491 $missing_body
492 }
493 fn from_syntax_node(db: &'db dyn Database, node: SyntaxNode<'db>) -> Self {
494 let kind = node.kind(db);
495 match kind{
496 $from_node_body
497 _ => panic!(
498 "Unexpected syntax kind {:?} when constructing {}.",
499 kind,
500 $[str]($[const](&name))),
501 }
502 }
503 fn cast(db: &'db dyn Database, node: SyntaxNode<'db>) -> Option<Self> {
504 let kind = node.kind(db);
505 match kind {
506 $cast_body
507 _ => None,
508 }
509 }
510 fn as_syntax_node(&self) -> SyntaxNode<'db> {
511 match self {
512 $(for v in &variants => $(&name)::$(&v.name)(x) => x.as_syntax_node(),)
513 }
514 }
515 fn stable_ptr(&self, db: &'db dyn Database) -> Self::StablePtr {
516 $(&ptr_name)(self.as_syntax_node().stable_ptr(db))
517 }
518 }
519 impl<'db> $(&name)<'db> {
520 $("/// Checks if a kind of a variant of [")$(&name)$("].")
521 pub fn is_variant(kind: SyntaxKind) -> bool {
522 matches!(kind, $(for v in &variants join (|) => SyntaxKind::$(&v.kind)))
523 }
524 }
525 }
526}
527
528fn gen_token_code(name: String) -> rust::Tokens {
529 let green_name = format!("{name}Green");
530 let ptr_name = format!("{name}Ptr");
531
532 quote! {
533 #[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
534 pub struct $(&name)<'db> {
535 node: SyntaxNode<'db>,
536 }
537 impl<'db> Token<'db> for $(&name)<'db> {
538 fn new_green(db: &'db dyn Database, text: SmolStrId<'db>) -> Self::Green {
539 $(&green_name)(GreenNode {
540 kind: SyntaxKind::$(&name),
541 details: GreenNodeDetails::Token(text),
542 }.intern(db))
543 }
544 fn text(&self, db: &'db dyn Database) -> SmolStrId<'db> {
545 *extract_matches!(&self.node.green_node(db).details,
546 GreenNodeDetails::Token)
547 }
548 }
549 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update, HeapSize)]
550 pub struct $(&ptr_name)<'db>(pub SyntaxStablePtrId<'db>);
551 impl<'db> TypedStablePtr<'db> for $(&ptr_name)<'db> {
552 type SyntaxNode = $(&name)<'db>;
553 fn untyped(self) -> SyntaxStablePtrId<'db> {
554 self.0
555 }
556 fn lookup(&self, db: &'db dyn Database) -> $(&name)<'db> {
557 $(&name)::from_syntax_node(db, self.0.lookup(db))
558 }
559 }
560 impl<'db> From<$(&ptr_name)<'db>> for SyntaxStablePtrId<'db> {
561 fn from(ptr: $(&ptr_name)<'db>) -> Self {
562 ptr.untyped()
563 }
564 }
565 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update)]
566 pub struct $(&green_name)<'db>(pub GreenId<'db>);
567 impl<'db> $(&green_name)<'db> {
568 pub fn text(&self, db: &'db dyn Database) -> SmolStrId<'db> {
569 *extract_matches!(&self.0.long(db).details, GreenNodeDetails::Token)
570 }
571 }
572 impl<'db> TypedSyntaxNode<'db> for $(&name)<'db>{
573 const OPTIONAL_KIND: Option<SyntaxKind> = Some(SyntaxKind::$(&name));
574 type StablePtr = $(&ptr_name)<'db>;
575 type Green = $(&green_name)<'db>;
576 fn missing(db: &'db dyn Database) -> Self::Green {
577 $(&green_name)(GreenNode {
578 kind: SyntaxKind::TokenMissing,
579 details: GreenNodeDetails::Token(SmolStrId::from(db, "")),
580 }.intern(db))
581 }
582 fn from_syntax_node(db: &'db dyn Database, node: SyntaxNode<'db>) -> Self {
583 match node.green_node(db).details {
584 GreenNodeDetails::Token(_) => Self { node },
585 GreenNodeDetails::Node { .. } => panic!(
586 "Expected a token {:?}, not an internal node",
587 SyntaxKind::$(&name)
588 ),
589 }
590 }
591 fn cast(db: &'db dyn Database, node: SyntaxNode<'db>) -> Option<Self> {
592 match node.green_node(db).details {
593 GreenNodeDetails::Token(_) => Some(Self { node }),
594 GreenNodeDetails::Node { .. } => None,
595 }
596 }
597 fn as_syntax_node(&self) -> SyntaxNode<'db> {
598 self.node
599 }
600 fn stable_ptr(&self, db: &'db dyn Database) -> Self::StablePtr {
601 $(&ptr_name)(self.node.stable_ptr(db))
602 }
603 }
604 }
605}
606
607fn gen_struct_code(name: String, members: Vec<Member>, is_terminal: bool) -> rust::Tokens {
608 let green_name = format!("{name}Green");
609 let mut body = rust::Tokens::new();
610 let mut field_indices = quote! {};
611 let mut args = quote! {};
612 let mut params = quote! {};
613 let mut args_for_missing = quote! {};
614 let mut ptr_getters = quote! {};
615 let mut key_field_index: usize = 0;
616 for (i, Member { name, kind, key }) in members.iter().enumerate() {
617 let index_name = format!("INDEX_{}", name.to_uppercase());
618 field_indices.extend(quote! {
619 pub const $index_name : usize = $i;
620 });
621 let key_name_green = format!("{name}_green");
622 args.extend(quote! {$name.0,});
623 let child_green = format!("{kind}Green");
626 params.extend(quote! {$name: $(&child_green)<'db>,});
627 body.extend(quote! {
628 pub fn $name(&self, db: &'db dyn Database) -> $kind<'db> {
629 $kind::from_syntax_node(db, self.node.get_children(db)[$i])
630 }
631 });
632 args_for_missing.extend(quote! {$kind::missing(db).0,});
633
634 if *key {
635 ptr_getters.extend(quote! {
636 pub fn $(&key_name_green)(self, db: &'db dyn Database) -> $(&child_green)<'db> {
637 $(&child_green)(self.0.0.key_fields(db)[$key_field_index])
638 }
639 });
640 key_field_index += 1;
641 }
642 }
643 let ptr_name = format!("{name}Ptr");
644 let new_green_impl = if is_terminal {
645 let token_name = name.replace("Terminal", "Token");
646 quote! {
647 impl<'db> Terminal<'db> for $(&name)<'db> {
648 const KIND: SyntaxKind = SyntaxKind::$(&name);
649 type TokenType = $(&token_name)<'db>;
650 fn new_green(
651 db: &'db dyn Database,
652 leading_trivia: TriviaGreen<'db>,
653 token: <<$(&name)<'db> as Terminal<'db>>::TokenType as TypedSyntaxNode<'db>>::Green,
654 trailing_trivia: TriviaGreen<'db>
655 ) -> Self::Green {
656 let children = [$args];
657 let width =
658 children.into_iter().map(|id: GreenId<'_>| id.long(db).width(db)).sum();
659 $(&green_name)(GreenNode {
660 kind: SyntaxKind::$(&name),
661 details: GreenNodeDetails::Node { children: children.into(), width },
662 }.intern(db))
663 }
664 fn text(&self, db: &'db dyn Database) -> SmolStrId<'db> {
665 let GreenNodeDetails::Node{children,..} = &self.node.green_node(db).details else {
666 unreachable!("Expected a node, not a token");
667 };
668 *extract_matches!(&children[1].long(db).details, GreenNodeDetails::Token)
669 }
670 }
671 }
672 } else {
673 quote! {
674 impl<'db> $(&name)<'db> {
675 $field_indices
676 pub fn new_green(db: &'db dyn Database, $params) -> $(&green_name)<'db> {
677 let children = [$args];
678 let width =
679 children.into_iter().map(|id: GreenId<'_>| id.long(db).width(db)).sum();
680 $(&green_name)(GreenNode {
681 kind: SyntaxKind::$(&name),
682 details: GreenNodeDetails::Node { children: children.into(), width },
683 }.intern(db))
684 }
685 }
686 }
687 };
688 quote! {
689 #[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
690 pub struct $(&name)<'db> {
691 node: SyntaxNode<'db>,
692 }
693 $new_green_impl
694 impl<'db> $(&name)<'db> {
695 $body
696 }
697 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update, HeapSize)]
698 pub struct $(&ptr_name)<'db>(pub SyntaxStablePtrId<'db>);
699 impl<'db> $(&ptr_name)<'db> {
700 $ptr_getters
701 }
702 impl<'db> TypedStablePtr<'db> for $(&ptr_name)<'db> {
703 type SyntaxNode = $(&name)<'db>;
704 fn untyped(self) -> SyntaxStablePtrId<'db> {
705 self.0
706 }
707 fn lookup(&self, db: &'db dyn Database) -> $(&name)<'db> {
708 $(&name)::from_syntax_node(db, self.0.lookup(db))
709 }
710 }
711 impl<'db> From<$(&ptr_name)<'db>> for SyntaxStablePtrId<'db> {
712 fn from(ptr: $(&ptr_name)<'db>) -> Self {
713 ptr.untyped()
714 }
715 }
716 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update)]
717 pub struct $(&green_name)<'db>(pub GreenId<'db>);
718 impl<'db> TypedSyntaxNode<'db> for $(&name)<'db> {
719 const OPTIONAL_KIND: Option<SyntaxKind> = Some(SyntaxKind::$(&name));
720 type StablePtr = $(&ptr_name)<'db>;
721 type Green = $(&green_name)<'db>;
722 fn missing(db: &'db dyn Database) -> Self::Green {
723 $(&green_name)(GreenNode {
726 kind: SyntaxKind::$(&name),
727 details: GreenNodeDetails::Node {
728 children: [$args_for_missing].into(),
729 width: TextWidth::default(),
730 },
731 }.intern(db))
732 }
733 fn from_syntax_node(db: &'db dyn Database, node: SyntaxNode<'db>) -> Self {
734 let kind = node.kind(db);
735 assert_eq!(kind, SyntaxKind::$(&name), "Unexpected SyntaxKind {:?}. Expected {:?}.", kind, SyntaxKind::$(&name));
736 Self { node }
737 }
738 fn cast(db: &'db dyn Database, node: SyntaxNode<'db>) -> Option<Self> {
739 let kind = node.kind(db);
740 if kind == SyntaxKind::$(&name) {
741 Some(Self::from_syntax_node(db, node))
742 } else {
743 None
744 }
745 }
746 fn as_syntax_node(&self) -> SyntaxNode<'db> {
747 self.node
748 }
749 fn stable_ptr(&self, db: &'db dyn Database) -> Self::StablePtr {
750 $(&ptr_name)(self.node.stable_ptr(db))
751 }
752 }
753 }
754}