fork of https://github.com/tree-sitter/tree-sitter-graph
1// -*- coding: utf-8 -*-
2// ------------------------------------------------------------------------------------------------
3// Copyright © 2021, tree-sitter authors.
4// Licensed under either of Apache License, Version 2.0, or MIT license, at your option.
5// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details.
6// ------------------------------------------------------------------------------------------------
7
8//! Defines the AST structure of a graph DSL file
9
10use regex::Regex;
11use std::collections::HashMap;
12use std::fmt;
13use tree_sitter::CaptureQuantifier;
14use tree_sitter::Language;
15use tree_sitter::Query;
16
17use crate::Identifier;
18use crate::Location;
19
20/// A graph DSL file
21#[derive(Debug)]
22pub struct File {
23 pub language: Language,
24 /// The expected global variables used in this file
25 pub globals: Vec<Global>,
26 /// The combined query of all stanzas in the file
27 pub query: Option<Query>,
28 /// The list of stanzas in the file
29 pub stanzas: Vec<Stanza>,
30 /// Attribute shorthands defined in the file
31 pub shorthands: AttributeShorthands,
32}
33
34impl File {
35 pub fn new(language: Language) -> File {
36 File {
37 language,
38 globals: Vec::new(),
39 query: None,
40 stanzas: Vec::new(),
41 shorthands: AttributeShorthands::new(),
42 }
43 }
44}
45
46/// A global variable
47#[derive(Debug, Eq, PartialEq)]
48pub struct Global {
49 /// The name of the global variable
50 pub name: Identifier,
51 /// The quantifier of the global variable
52 pub quantifier: CaptureQuantifier,
53 /// Default value
54 pub default: Option<String>,
55 pub location: Location,
56}
57
58/// One stanza within a file
59#[derive(Debug)]
60pub struct Stanza {
61 /// The tree-sitter query for this stanza
62 pub query: Query,
63 /// The list of statements in the stanza
64 pub statements: Vec<Statement>,
65 /// Capture index of the full match in the stanza query
66 pub full_match_stanza_capture_index: usize,
67 /// Capture index of the full match in the file query
68 pub full_match_file_capture_index: usize,
69 pub location: Location,
70}
71
72/// A statement that can appear in a graph DSL stanza
73#[derive(Debug, Eq, PartialEq)]
74pub enum Statement {
75 // Variables
76 DeclareImmutable(DeclareImmutable),
77 DeclareMutable(DeclareMutable),
78 Assign(Assign),
79 // Graph nodes
80 CreateGraphNode(CreateGraphNode),
81 AddGraphNodeAttribute(AddGraphNodeAttribute),
82 // Edges
83 CreateEdge(CreateEdge),
84 AddEdgeAttribute(AddEdgeAttribute),
85 // Regular expression
86 Scan(Scan),
87 // Debugging
88 Print(Print),
89 // If
90 If(If),
91 // ForIn
92 ForIn(ForIn),
93}
94
95impl std::fmt::Display for Statement {
96 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
97 match self {
98 Self::DeclareImmutable(stmt) => stmt.fmt(f),
99 Self::DeclareMutable(stmt) => stmt.fmt(f),
100 Self::Assign(stmt) => stmt.fmt(f),
101 Self::CreateGraphNode(stmt) => stmt.fmt(f),
102 Self::AddGraphNodeAttribute(stmt) => stmt.fmt(f),
103 Self::CreateEdge(stmt) => stmt.fmt(f),
104 Self::AddEdgeAttribute(stmt) => stmt.fmt(f),
105 Self::Scan(stmt) => stmt.fmt(f),
106 Self::Print(stmt) => stmt.fmt(f),
107 Self::If(stmt) => stmt.fmt(f),
108 Self::ForIn(stmt) => stmt.fmt(f),
109 }
110 }
111}
112
113/// An `attr` statement that adds an attribute to an edge
114#[derive(Debug, Eq, PartialEq)]
115pub struct AddEdgeAttribute {
116 pub source: Expression,
117 pub sink: Expression,
118 pub attributes: Vec<Attribute>,
119 pub location: Location,
120}
121
122impl From<AddEdgeAttribute> for Statement {
123 fn from(statement: AddEdgeAttribute) -> Statement {
124 Statement::AddEdgeAttribute(statement)
125 }
126}
127
128impl std::fmt::Display for AddEdgeAttribute {
129 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
130 write!(f, "attr ({} -> {})", self.source, self.sink)?;
131 for attr in &self.attributes {
132 write!(f, " {}", attr)?;
133 }
134 write!(f, " at {}", self.location)
135 }
136}
137
138/// An `attr` statement that adds an attribute to a graph node
139#[derive(Debug, Eq, PartialEq)]
140pub struct AddGraphNodeAttribute {
141 pub node: Expression,
142 pub attributes: Vec<Attribute>,
143 pub location: Location,
144}
145
146impl From<AddGraphNodeAttribute> for Statement {
147 fn from(statement: AddGraphNodeAttribute) -> Statement {
148 Statement::AddGraphNodeAttribute(statement)
149 }
150}
151
152impl std::fmt::Display for AddGraphNodeAttribute {
153 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
154 write!(f, "attr ({})", self.node)?;
155 for attr in &self.attributes {
156 write!(f, " {}", attr)?;
157 }
158 write!(f, " at {}", self.location)
159 }
160}
161
162/// A `set` statement that updates the value of a mutable variable
163#[derive(Debug, Eq, PartialEq)]
164pub struct Assign {
165 pub variable: Variable,
166 pub value: Expression,
167 pub location: Location,
168}
169
170impl From<Assign> for Statement {
171 fn from(statement: Assign) -> Statement {
172 Statement::Assign(statement)
173 }
174}
175
176impl std::fmt::Display for Assign {
177 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
178 write!(
179 f,
180 "set {} = {} at {}",
181 self.variable, self.value, self.location,
182 )
183 }
184}
185
186/// The name and value of an attribute
187#[derive(Debug, Eq, PartialEq)]
188pub struct Attribute {
189 pub name: Identifier,
190 pub value: Expression,
191}
192
193impl std::fmt::Display for Attribute {
194 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
195 write!(f, "{} = {}", self.name, self.value)
196 }
197}
198
199/// An `edge` statement that creates a new edge
200#[derive(Debug, Eq, PartialEq)]
201pub struct CreateEdge {
202 pub source: Expression,
203 pub sink: Expression,
204 pub location: Location,
205}
206
207impl From<CreateEdge> for Statement {
208 fn from(statement: CreateEdge) -> Statement {
209 Statement::CreateEdge(statement)
210 }
211}
212
213impl std::fmt::Display for CreateEdge {
214 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
215 write!(
216 f,
217 "edge {} -> {} at {}",
218 self.source, self.sink, self.location,
219 )
220 }
221}
222
223/// A `node` statement that creates a new graph node
224#[derive(Debug, Eq, PartialEq)]
225pub struct CreateGraphNode {
226 pub node: Variable,
227 pub location: Location,
228}
229
230impl From<CreateGraphNode> for Statement {
231 fn from(statement: CreateGraphNode) -> Statement {
232 Statement::CreateGraphNode(statement)
233 }
234}
235
236impl std::fmt::Display for CreateGraphNode {
237 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
238 write!(f, "node {} at {}", self.node, self.location)
239 }
240}
241
242/// A `let` statement that declares a new immutable variable
243#[derive(Debug, Eq, PartialEq)]
244pub struct DeclareImmutable {
245 pub variable: Variable,
246 pub value: Expression,
247 pub location: Location,
248}
249
250impl From<DeclareImmutable> for Statement {
251 fn from(statement: DeclareImmutable) -> Statement {
252 Statement::DeclareImmutable(statement)
253 }
254}
255
256impl std::fmt::Display for DeclareImmutable {
257 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
258 write!(
259 f,
260 "let {} = {} at {}",
261 self.variable, self.value, self.location,
262 )
263 }
264}
265
266/// A `var` statement that declares a new mutable variable
267#[derive(Debug, Eq, PartialEq)]
268pub struct DeclareMutable {
269 pub variable: Variable,
270 pub value: Expression,
271 pub location: Location,
272}
273
274impl From<DeclareMutable> for Statement {
275 fn from(statement: DeclareMutable) -> Statement {
276 Statement::DeclareMutable(statement)
277 }
278}
279
280impl std::fmt::Display for DeclareMutable {
281 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
282 write!(
283 f,
284 "var {} = {} at {}",
285 self.variable, self.value, self.location,
286 )
287 }
288}
289
290/// A `print` statement that prints out some debugging information
291#[derive(Debug, Eq, PartialEq)]
292pub struct Print {
293 pub values: Vec<Expression>,
294 pub location: Location,
295}
296
297impl From<Print> for Statement {
298 fn from(statement: Print) -> Statement {
299 Statement::Print(statement)
300 }
301}
302
303impl std::fmt::Display for Print {
304 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
305 write!(f, "print")?;
306 for val in &self.values {
307 write!(f, " {},", val)?;
308 }
309 write!(f, " at {}", self.location)
310 }
311}
312
313/// A `scan` statement that matches regular expressions against a string
314#[derive(Debug, Eq, PartialEq)]
315pub struct Scan {
316 pub value: Expression,
317 pub arms: Vec<ScanArm>,
318 pub location: Location,
319}
320
321impl From<Scan> for Statement {
322 fn from(statement: Scan) -> Statement {
323 Statement::Scan(statement)
324 }
325}
326
327impl std::fmt::Display for Scan {
328 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
329 write!(f, "scan {} {{ ... }} at {}", self.value, self.location)
330 }
331}
332
333/// One arm of a `scan` statement
334#[derive(Debug)]
335pub struct ScanArm {
336 pub regex: Regex,
337 pub statements: Vec<Statement>,
338 pub location: Location,
339}
340
341impl Eq for ScanArm {}
342
343impl PartialEq for ScanArm {
344 fn eq(&self, other: &ScanArm) -> bool {
345 self.regex.as_str() == other.regex.as_str() && self.statements == other.statements
346 }
347}
348
349impl std::fmt::Display for ScanArm {
350 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
351 write!(f, "{:?} {{ ... }}", self.regex.as_str())
352 }
353}
354
355/// A `cond` conditional statement that selects the first branch with a matching condition
356#[derive(Debug, Eq, PartialEq)]
357pub struct If {
358 pub arms: Vec<IfArm>,
359 pub location: Location,
360}
361
362impl From<If> for Statement {
363 fn from(statement: If) -> Statement {
364 Statement::If(statement)
365 }
366}
367
368impl std::fmt::Display for If {
369 fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result {
370 let mut first = true;
371 for arm in &self.arms {
372 if first {
373 first = false;
374 write!(f, "if {} {{ ... }}", DisplayConditions(&arm.conditions))?;
375 } else {
376 if !arm.conditions.is_empty() {
377 write!(f, " elif {} {{ ... }}", DisplayConditions(&arm.conditions))?;
378 } else {
379 write!(f, " else {{ ... }}")?;
380 }
381 }
382 }
383 write!(f, " at {}", self.location)
384 }
385}
386
387/// One arm of a `cond` statement
388#[derive(Debug, PartialEq, Eq)]
389pub struct IfArm {
390 pub conditions: Vec<Condition>,
391 pub statements: Vec<Statement>,
392 pub location: Location,
393}
394
395struct DisplayConditions<'a>(&'a Vec<Condition>);
396
397#[derive(Debug, PartialEq, Eq)]
398pub enum Condition {
399 Some {
400 value: Expression,
401 location: Location,
402 },
403 None {
404 value: Expression,
405 location: Location,
406 },
407 Bool {
408 value: Expression,
409 location: Location,
410 },
411}
412
413impl std::fmt::Display for DisplayConditions<'_> {
414 fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result {
415 let mut first = true;
416 for condition in self.0.iter() {
417 if first {
418 first = false;
419 write!(f, "{}", condition)?;
420 } else {
421 write!(f, ", {}", condition)?;
422 }
423 }
424 Ok(())
425 }
426}
427
428impl std::fmt::Display for Condition {
429 fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result {
430 match self {
431 Condition::Some { value, .. } => {
432 write!(f, "some {}", value)
433 }
434 Condition::None { value, .. } => {
435 write!(f, "none {}", value)
436 }
437 Condition::Bool { value, .. } => {
438 write!(f, "{}", value)
439 }
440 }
441 }
442}
443
444/// A `for in` statement
445#[derive(Debug, Eq, PartialEq)]
446pub struct ForIn {
447 pub variable: UnscopedVariable,
448 pub value: Expression,
449 pub statements: Vec<Statement>,
450 pub location: Location,
451}
452
453impl From<ForIn> for Statement {
454 fn from(statement: ForIn) -> Statement {
455 Statement::ForIn(statement)
456 }
457}
458
459impl std::fmt::Display for ForIn {
460 fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result {
461 write!(
462 f,
463 "for {} in {} {{ ... }} at {}",
464 self.variable, self.value, self.location,
465 )
466 }
467}
468
469/// A reference to a variable
470#[derive(Debug, Eq, PartialEq)]
471pub enum Variable {
472 Scoped(ScopedVariable),
473 Unscoped(UnscopedVariable),
474}
475
476impl std::fmt::Display for Variable {
477 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
478 match self {
479 Variable::Scoped(variable) => variable.fmt(f),
480 Variable::Unscoped(variable) => variable.fmt(f),
481 }
482 }
483}
484
485/// A reference to a scoped variable
486#[derive(Debug, Eq, PartialEq)]
487pub struct ScopedVariable {
488 pub scope: Box<Expression>,
489 pub name: Identifier,
490 pub location: Location,
491}
492
493impl From<ScopedVariable> for Variable {
494 fn from(variable: ScopedVariable) -> Variable {
495 Variable::Scoped(variable)
496 }
497}
498
499impl std::fmt::Display for ScopedVariable {
500 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
501 write!(f, "{}.{}", self.scope, self.name)
502 }
503}
504
505/// A reference to a global or local variable
506#[derive(Debug, Eq, PartialEq)]
507pub struct UnscopedVariable {
508 pub name: Identifier,
509 pub location: Location,
510}
511
512impl From<UnscopedVariable> for Variable {
513 fn from(variable: UnscopedVariable) -> Variable {
514 Variable::Unscoped(variable)
515 }
516}
517
518impl std::fmt::Display for UnscopedVariable {
519 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
520 write!(f, "{}", self.name)
521 }
522}
523
524/// An expression that can appear in a graph DSL file
525#[derive(Debug, Eq, PartialEq)]
526pub enum Expression {
527 // Literals
528 FalseLiteral,
529 NullLiteral,
530 TrueLiteral,
531 // Constants
532 IntegerConstant(IntegerConstant),
533 StringConstant(StringConstant),
534 // Literals
535 ListLiteral(ListLiteral),
536 SetLiteral(SetLiteral),
537 // Comprehensions
538 ListComprehension(ListComprehension),
539 SetComprehension(SetComprehension),
540 // Syntax nodes
541 Capture(Capture),
542 // Variables
543 Variable(Variable),
544 // Functions
545 Call(Call),
546 // Regular expression
547 RegexCapture(RegexCapture),
548}
549
550impl std::fmt::Display for Expression {
551 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
552 match self {
553 Expression::FalseLiteral => write!(f, "false"),
554 Expression::NullLiteral => write!(f, "#null"),
555 Expression::TrueLiteral => write!(f, "true"),
556 Expression::IntegerConstant(expr) => expr.fmt(f),
557 Expression::StringConstant(expr) => expr.fmt(f),
558 Expression::ListLiteral(expr) => expr.fmt(f),
559 Expression::SetLiteral(expr) => expr.fmt(f),
560 Expression::ListComprehension(expr) => expr.fmt(f),
561 Expression::SetComprehension(expr) => expr.fmt(f),
562 Expression::Capture(expr) => expr.fmt(f),
563 Expression::Variable(expr) => expr.fmt(f),
564 Expression::Call(expr) => expr.fmt(f),
565 Expression::RegexCapture(expr) => expr.fmt(f),
566 }
567 }
568}
569
570/// A function call
571#[derive(Debug, Eq, PartialEq)]
572pub struct Call {
573 pub function: Identifier,
574 pub parameters: Vec<Expression>,
575}
576
577impl From<Call> for Expression {
578 fn from(expr: Call) -> Expression {
579 Expression::Call(expr)
580 }
581}
582
583impl std::fmt::Display for Call {
584 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
585 write!(f, "({}", self.function)?;
586 for arg in &self.parameters {
587 write!(f, " {}", arg)?;
588 }
589 write!(f, ")")
590 }
591}
592
593/// A capture expression that references a syntax node
594#[derive(Debug, Eq, PartialEq)]
595pub struct Capture {
596 /// The name of the capture
597 pub name: Identifier,
598 /// The suffix of the capture
599 pub quantifier: CaptureQuantifier,
600 /// Capture index in the merged file query
601 pub file_capture_index: usize,
602 /// Capture index in the stanza query
603 pub stanza_capture_index: usize,
604 pub location: Location,
605}
606
607impl From<Capture> for Expression {
608 fn from(expr: Capture) -> Expression {
609 Expression::Capture(expr)
610 }
611}
612
613impl std::fmt::Display for Capture {
614 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
615 write!(f, "@{}", self.name)
616 }
617}
618
619/// An integer constant
620#[derive(Debug, Eq, PartialEq)]
621pub struct IntegerConstant {
622 pub value: u32,
623}
624
625impl From<IntegerConstant> for Expression {
626 fn from(expr: IntegerConstant) -> Expression {
627 Expression::IntegerConstant(expr)
628 }
629}
630
631impl std::fmt::Display for IntegerConstant {
632 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
633 write!(f, "{}", self.value)
634 }
635}
636
637/// An ordered list of values
638#[derive(Debug, Eq, PartialEq)]
639pub struct ListLiteral {
640 pub elements: Vec<Expression>,
641}
642
643impl From<ListLiteral> for Expression {
644 fn from(expr: ListLiteral) -> Expression {
645 Expression::ListLiteral(expr)
646 }
647}
648
649impl std::fmt::Display for ListLiteral {
650 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
651 write!(f, "[")?;
652 let mut first = true;
653 for elem in &self.elements {
654 if first {
655 write!(f, "{}", elem)?;
656 first = false;
657 } else {
658 write!(f, ", {}", elem)?;
659 }
660 }
661 write!(f, "]")
662 }
663}
664
665/// An list comprehension
666#[derive(Debug, Eq, PartialEq)]
667pub struct ListComprehension {
668 pub element: Box<Expression>,
669 pub variable: UnscopedVariable,
670 pub value: Box<Expression>,
671 pub location: Location,
672}
673
674impl From<ListComprehension> for Expression {
675 fn from(expr: ListComprehension) -> Expression {
676 Expression::ListComprehension(expr)
677 }
678}
679
680impl std::fmt::Display for ListComprehension {
681 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
682 write!(
683 f,
684 "[ {} for {} in {} ]",
685 self.element, self.variable, self.value
686 )
687 }
688}
689
690/// A reference to one of the regex captures in a `scan` statement
691#[derive(Debug, Eq, PartialEq)]
692pub struct RegexCapture {
693 pub match_index: usize,
694}
695
696impl From<RegexCapture> for Expression {
697 fn from(expr: RegexCapture) -> Expression {
698 Expression::RegexCapture(expr)
699 }
700}
701
702impl std::fmt::Display for RegexCapture {
703 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
704 write!(f, "${}", self.match_index)
705 }
706}
707
708/// An unordered set of values
709#[derive(Debug, Eq, PartialEq)]
710pub struct SetLiteral {
711 pub elements: Vec<Expression>,
712}
713
714impl From<SetLiteral> for Expression {
715 fn from(expr: SetLiteral) -> Expression {
716 Expression::SetLiteral(expr)
717 }
718}
719
720impl std::fmt::Display for SetLiteral {
721 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
722 write!(f, "{{")?;
723 let mut first = true;
724 for elem in &self.elements {
725 if first {
726 write!(f, "{}", elem)?;
727 first = false;
728 } else {
729 write!(f, ", {}", elem)?;
730 }
731 }
732 write!(f, "}}")
733 }
734}
735
736/// An set comprehension
737#[derive(Debug, Eq, PartialEq)]
738pub struct SetComprehension {
739 pub element: Box<Expression>,
740 pub variable: UnscopedVariable,
741 pub value: Box<Expression>,
742 pub location: Location,
743}
744
745impl From<SetComprehension> for Expression {
746 fn from(expr: SetComprehension) -> Expression {
747 Expression::SetComprehension(expr)
748 }
749}
750
751impl std::fmt::Display for SetComprehension {
752 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
753 write!(
754 f,
755 "{{ {} for {} in {} }}",
756 self.element, self.variable, self.value
757 )
758 }
759}
760
761/// A string constant
762#[derive(Debug, Eq, PartialEq)]
763pub struct StringConstant {
764 pub value: String,
765}
766
767impl From<StringConstant> for Expression {
768 fn from(expr: StringConstant) -> Expression {
769 Expression::StringConstant(expr)
770 }
771}
772
773impl std::fmt::Display for StringConstant {
774 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
775 write!(f, "{:?}", self.value)
776 }
777}
778
779impl From<String> for Expression {
780 fn from(value: String) -> Expression {
781 Expression::StringConstant(StringConstant { value }.into())
782 }
783}
784
785impl From<UnscopedVariable> for Expression {
786 fn from(variable: UnscopedVariable) -> Expression {
787 Expression::Variable(variable.into())
788 }
789}
790
791impl From<ScopedVariable> for Expression {
792 fn from(variable: ScopedVariable) -> Expression {
793 Expression::Variable(variable.into())
794 }
795}
796
797/// Attribute shorthands
798#[derive(Debug, Eq, PartialEq)]
799pub struct AttributeShorthands(HashMap<Identifier, AttributeShorthand>);
800
801impl AttributeShorthands {
802 pub fn new() -> Self {
803 Self(HashMap::new())
804 }
805
806 pub fn get(&self, name: &Identifier) -> Option<&AttributeShorthand> {
807 self.0.get(name)
808 }
809
810 pub fn add(&mut self, shorthand: AttributeShorthand) {
811 self.0.insert(shorthand.name.clone(), shorthand);
812 }
813
814 pub fn iter(&self) -> impl Iterator<Item = &AttributeShorthand> {
815 self.0.values()
816 }
817
818 pub fn into_iter(self) -> impl Iterator<Item = AttributeShorthand> {
819 self.0.into_values()
820 }
821}
822
823/// An attribute shorthand
824#[derive(Debug, Eq, PartialEq)]
825pub struct AttributeShorthand {
826 pub name: Identifier,
827 pub variable: UnscopedVariable,
828 pub attributes: Vec<Attribute>,
829 pub location: Location,
830}
831
832impl std::fmt::Display for AttributeShorthand {
833 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
834 write!(f, "attribute {} = {} =>", self.name, self.variable,)?;
835 for attr in &self.attributes {
836 write!(f, " {}", attr)?;
837 }
838 write!(f, " at {}", self.location)
839 }
840}