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