1mod generated;
22
23pub use generated::*;
24
25use std::marker::PhantomData;
26
27use rowan::TextRange;
28
29use crate::syntax_kind::{RidlLanguage, SyntaxKind, SyntaxNode, SyntaxToken};
30
31pub trait AstNode: Sized {
33 fn cast(syntax: SyntaxNode) -> Option<Self>;
35
36 fn syntax(&self) -> &SyntaxNode;
38}
39
40#[derive(Debug, Clone)]
43pub struct AstChildren<N> {
44 inner: rowan::SyntaxNodeChildren<RidlLanguage>,
45 _marker: PhantomData<N>,
46}
47
48impl<N> AstChildren<N> {
49 fn new(parent: &SyntaxNode) -> Self {
50 Self {
51 inner: parent.children(),
52 _marker: PhantomData,
53 }
54 }
55}
56
57impl<N: AstNode> Iterator for AstChildren<N> {
58 type Item = N;
59
60 fn next(&mut self) -> Option<N> {
61 self.inner.by_ref().find_map(N::cast)
62 }
63}
64
65mod support {
68 use super::{AstChildren, AstNode};
69 use crate::syntax_kind::{SyntaxKind, SyntaxNode, SyntaxToken};
70
71 pub(super) fn child<N: AstNode>(parent: &SyntaxNode) -> Option<N> {
73 parent.children().find_map(N::cast)
74 }
75
76 pub(super) fn nth_child<N: AstNode>(parent: &SyntaxNode, n: usize) -> Option<N> {
78 parent.children().filter_map(N::cast).nth(n)
79 }
80
81 pub(super) fn children<N: AstNode>(parent: &SyntaxNode) -> AstChildren<N> {
83 AstChildren::new(parent)
84 }
85
86 pub(super) fn token(parent: &SyntaxNode, kind: SyntaxKind) -> Option<SyntaxToken> {
88 parent
89 .children_with_tokens()
90 .filter_map(|element| element.into_token())
91 .find(|token| token.kind() == kind)
92 }
93}
94
95#[derive(Debug, Clone, PartialEq, Eq, Hash)]
99pub enum Definition {
100 Type(TypeDef),
101 Const(ConstDef),
102 Struct(StructDef),
103 Enum(EnumDef),
104 EnumSet(EnumSetDef),
105 Union(UnionDef),
106}
107
108impl AstNode for Definition {
109 fn cast(syntax: SyntaxNode) -> Option<Self> {
110 match syntax.kind() {
111 SyntaxKind::TypeDef => TypeDef::cast(syntax).map(Self::Type),
112 SyntaxKind::ConstDef => ConstDef::cast(syntax).map(Self::Const),
113 SyntaxKind::StructDef => StructDef::cast(syntax).map(Self::Struct),
114 SyntaxKind::EnumDef => EnumDef::cast(syntax).map(Self::Enum),
115 SyntaxKind::EnumSetDef => EnumSetDef::cast(syntax).map(Self::EnumSet),
116 SyntaxKind::UnionDef => UnionDef::cast(syntax).map(Self::Union),
117 _ => None,
118 }
119 }
120
121 fn syntax(&self) -> &SyntaxNode {
122 match self {
123 Self::Type(it) => it.syntax(),
124 Self::Const(it) => it.syntax(),
125 Self::Struct(it) => it.syntax(),
126 Self::Enum(it) => it.syntax(),
127 Self::EnumSet(it) => it.syntax(),
128 Self::Union(it) => it.syntax(),
129 }
130 }
131}
132
133#[derive(Debug, Clone, PartialEq, Eq, Hash)]
136pub enum Backing {
137 Primitive(PrimitiveType),
138 Unit(UnitExpr),
139}
140
141impl AstNode for Backing {
142 fn cast(syntax: SyntaxNode) -> Option<Self> {
143 match syntax.kind() {
144 SyntaxKind::PrimitiveType => PrimitiveType::cast(syntax).map(Self::Primitive),
145 SyntaxKind::UnitExpr => UnitExpr::cast(syntax).map(Self::Unit),
146 _ => None,
147 }
148 }
149
150 fn syntax(&self) -> &SyntaxNode {
151 match self {
152 Self::Primitive(it) => it.syntax(),
153 Self::Unit(it) => it.syntax(),
154 }
155 }
156}
157
158#[derive(Debug, Clone, PartialEq, Eq, Hash)]
161pub enum StructMember {
162 Field(FieldDef),
163 Reserved(ReservedEntry),
164}
165
166impl AstNode for StructMember {
167 fn cast(syntax: SyntaxNode) -> Option<Self> {
168 match syntax.kind() {
169 SyntaxKind::FieldDef => FieldDef::cast(syntax).map(Self::Field),
170 SyntaxKind::ReservedEntry => ReservedEntry::cast(syntax).map(Self::Reserved),
171 _ => None,
172 }
173 }
174
175 fn syntax(&self) -> &SyntaxNode {
176 match self {
177 Self::Field(it) => it.syntax(),
178 Self::Reserved(it) => it.syntax(),
179 }
180 }
181}
182
183#[derive(Debug, Clone, PartialEq, Eq, Hash)]
186pub enum FieldType {
187 Path(PathType),
188 Primitive(PrimitiveType),
189 Tuple(TupleType),
190 Array(ArrayType),
191 Map(MapType),
192 Optional(OptionalType),
193}
194
195impl AstNode for FieldType {
196 fn cast(syntax: SyntaxNode) -> Option<Self> {
197 match syntax.kind() {
198 SyntaxKind::PathType => PathType::cast(syntax).map(Self::Path),
199 SyntaxKind::PrimitiveType => PrimitiveType::cast(syntax).map(Self::Primitive),
200 SyntaxKind::TupleType => TupleType::cast(syntax).map(Self::Tuple),
201 SyntaxKind::ArrayType => ArrayType::cast(syntax).map(Self::Array),
202 SyntaxKind::MapType => MapType::cast(syntax).map(Self::Map),
203 SyntaxKind::OptionalType => OptionalType::cast(syntax).map(Self::Optional),
204 _ => None,
205 }
206 }
207
208 fn syntax(&self) -> &SyntaxNode {
209 match self {
210 Self::Path(it) => it.syntax(),
211 Self::Primitive(it) => it.syntax(),
212 Self::Tuple(it) => it.syntax(),
213 Self::Array(it) => it.syntax(),
214 Self::Map(it) => it.syntax(),
215 Self::Optional(it) => it.syntax(),
216 }
217 }
218}
219
220#[derive(Debug, Clone, PartialEq, Eq, Hash)]
223pub enum InterfaceMember {
224 Signal(SignalDef),
225 Event(EventDef),
226 Command(CommandDef),
227 Query(QueryDef),
228 Fixed(FixedDef),
229 Reserved(ReservedEntry),
230}
231
232impl AstNode for InterfaceMember {
233 fn cast(syntax: SyntaxNode) -> Option<Self> {
234 match syntax.kind() {
235 SyntaxKind::SignalDef => SignalDef::cast(syntax).map(Self::Signal),
236 SyntaxKind::EventDef => EventDef::cast(syntax).map(Self::Event),
237 SyntaxKind::CommandDef => CommandDef::cast(syntax).map(Self::Command),
238 SyntaxKind::QueryDef => QueryDef::cast(syntax).map(Self::Query),
239 SyntaxKind::FixedDef => FixedDef::cast(syntax).map(Self::Fixed),
240 SyntaxKind::ReservedEntry => ReservedEntry::cast(syntax).map(Self::Reserved),
241 _ => None,
242 }
243 }
244
245 fn syntax(&self) -> &SyntaxNode {
246 match self {
247 Self::Signal(it) => it.syntax(),
248 Self::Event(it) => it.syntax(),
249 Self::Command(it) => it.syntax(),
250 Self::Query(it) => it.syntax(),
251 Self::Fixed(it) => it.syntax(),
252 Self::Reserved(it) => it.syntax(),
253 }
254 }
255}
256
257#[derive(Debug, Clone, PartialEq, Eq, Hash)]
260pub enum ParamType {
261 Field(FieldType),
262 Stream(StreamType),
263}
264
265impl AstNode for ParamType {
266 fn cast(syntax: SyntaxNode) -> Option<Self> {
267 if syntax.kind() == SyntaxKind::StreamType {
268 StreamType::cast(syntax).map(Self::Stream)
269 } else {
270 FieldType::cast(syntax).map(Self::Field)
271 }
272 }
273
274 fn syntax(&self) -> &SyntaxNode {
275 match self {
276 Self::Field(it) => it.syntax(),
277 Self::Stream(it) => it.syntax(),
278 }
279 }
280}
281
282#[derive(Debug, Clone, PartialEq, Eq, Hash)]
285pub enum Expr {
286 Binary(BinaryExpr),
287 Prefix(PrefixExpr),
288 Member(MemberExpr),
289 Path(PathExpr),
290 Paren(ParenExpr),
291 Literal(LiteralExpr),
292}
293
294impl AstNode for Expr {
295 fn cast(syntax: SyntaxNode) -> Option<Self> {
296 match syntax.kind() {
297 SyntaxKind::BinaryExpr => BinaryExpr::cast(syntax).map(Self::Binary),
298 SyntaxKind::PrefixExpr => PrefixExpr::cast(syntax).map(Self::Prefix),
299 SyntaxKind::MemberExpr => MemberExpr::cast(syntax).map(Self::Member),
300 SyntaxKind::PathExpr => PathExpr::cast(syntax).map(Self::Path),
301 SyntaxKind::ParenExpr => ParenExpr::cast(syntax).map(Self::Paren),
302 SyntaxKind::LiteralExpr => LiteralExpr::cast(syntax).map(Self::Literal),
303 _ => None,
304 }
305 }
306
307 fn syntax(&self) -> &SyntaxNode {
308 match self {
309 Self::Binary(it) => it.syntax(),
310 Self::Prefix(it) => it.syntax(),
311 Self::Member(it) => it.syntax(),
312 Self::Path(it) => it.syntax(),
313 Self::Paren(it) => it.syntax(),
314 Self::Literal(it) => it.syntax(),
315 }
316 }
317}
318
319pub trait HasName: AstNode {
323 fn name(&self) -> Option<Name> {
325 support::child(self.syntax())
326 }
327}
328
329pub trait HasModifiers: AstNode {
332 fn is_internal(&self) -> bool {
334 support::token(self.syntax(), SyntaxKind::InternalKw).is_some()
335 }
336
337 fn is_error(&self) -> bool {
339 support::token(self.syntax(), SyntaxKind::ErrorKw).is_some()
340 }
341}
342
343pub trait HasDocComments: AstNode {
346 fn doc_comments(&self) -> Vec<SyntaxToken> {
349 let mut out = Vec::new();
350 let mut cursor = self.syntax().prev_sibling_or_token();
351 while let Some(rowan::NodeOrToken::Token(token)) = cursor {
352 if !token.kind().is_trivia() {
353 break;
354 }
355 if token.kind() == SyntaxKind::DocComment {
356 out.push(token.clone());
357 }
358 cursor = token.prev_sibling_or_token();
359 }
360 out.reverse();
361 out
362 }
363}
364
365impl HasName for TypeDef {}
366impl HasName for ConstDef {}
367impl HasName for StructDef {}
368impl HasName for EnumDef {}
369impl HasName for EnumSetDef {}
370impl HasName for UnionDef {}
371impl HasName for Definition {}
372impl HasName for InterfaceDef {}
373impl HasName for SignalDef {}
374impl HasName for EventDef {}
375impl HasName for CommandDef {}
376impl HasName for QueryDef {}
377impl HasName for FixedDef {}
378impl HasName for InterfaceMember {}
379
380impl HasName for SystemDef {}
381impl HasName for ComponentDef {}
382impl HasName for DistributionDef {}
383impl HasName for DeploymentDef {}
384impl HasName for MachineDef {}
385
386impl HasModifiers for TypeDef {}
387impl HasModifiers for ConstDef {}
388impl HasModifiers for StructDef {}
389impl HasModifiers for EnumDef {}
390impl HasModifiers for EnumSetDef {}
391impl HasModifiers for UnionDef {}
392impl HasModifiers for Definition {}
393impl HasModifiers for InterfaceDef {}
394
395impl HasDocComments for TypeDef {}
396impl HasDocComments for ConstDef {}
397impl HasDocComments for StructDef {}
398impl HasDocComments for EnumDef {}
399impl HasDocComments for EnumSetDef {}
400impl HasDocComments for UnionDef {}
401impl HasDocComments for Definition {}
402impl HasDocComments for InterfaceDef {}
403impl HasDocComments for SignalDef {}
404impl HasDocComments for EventDef {}
405impl HasDocComments for CommandDef {}
406impl HasDocComments for QueryDef {}
407impl HasDocComments for FixedDef {}
408impl HasDocComments for InterfaceMember {}
409impl HasDocComments for ServiceDef {}
410
411impl HasDocComments for SystemDef {}
412impl HasDocComments for ComponentDef {}
413impl HasDocComments for ComponentLine {}
414impl HasDocComments for DistributionDef {}
415impl HasDocComments for DeploymentDef {}
416impl HasDocComments for MachineDef {}
417impl HasDocComments for MemberLine {}
418
419impl DottedName {
420 pub fn segments(&self) -> impl Iterator<Item = SyntaxToken> + '_ {
425 self.syntax()
426 .children_with_tokens()
427 .filter_map(|element| element.into_token())
428 .filter(|token| token.kind() == SyntaxKind::Ident)
429 }
430
431 pub fn text(&self) -> String {
435 self.syntax()
436 .children_with_tokens()
437 .filter_map(|element| element.into_token())
438 .filter(|token| !token.kind().is_trivia())
439 .map(|token| token.text().to_string())
440 .collect()
441 }
442}
443
444#[derive(Debug, Clone, PartialEq, Eq, Hash)]
457pub enum InterfaceShape {
458 Interface(InterfaceDef),
460 ServiceInline(ServiceDef),
462}
463
464impl InterfaceShape {
465 pub fn identity(&self) -> Option<String> {
470 match self {
471 Self::Interface(def) => Some(def.name()?.ident_token()?.text().to_string()),
472 Self::ServiceInline(def) => {
473 let text = def.name()?.text();
474 (!text.is_empty()).then_some(text)
475 }
476 }
477 }
478
479 pub fn identity_range(&self) -> Option<TextRange> {
483 match self {
484 Self::Interface(def) => Some(def.name()?.syntax().text_range()),
485 Self::ServiceInline(def) => Some(def.name()?.syntax().text_range()),
486 }
487 }
488
489 pub fn members(&self) -> AstChildren<InterfaceMember> {
492 match self {
493 Self::Interface(def) => def.members(),
494 Self::ServiceInline(def) => def.inline_members(),
495 }
496 }
497
498 pub fn syntax(&self) -> &SyntaxNode {
500 match self {
501 Self::Interface(def) => def.syntax(),
502 Self::ServiceInline(def) => def.syntax(),
503 }
504 }
505}
506
507impl SourceFile {
508 pub fn shapes(&self) -> impl Iterator<Item = InterfaceShape> + '_ {
520 self.interfaces().map(InterfaceShape::Interface).chain(
521 self.services()
522 .filter(|service| service.colon_token().is_none())
523 .map(InterfaceShape::ServiceInline),
524 )
525 }
526}
527
528#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
532pub enum ComponentLineKind {
533 Offers,
535 Requires,
537}
538
539impl ComponentLine {
540 pub fn kind(&self) -> Option<ComponentLineKind> {
543 if self.offers_token().is_some() {
544 Some(ComponentLineKind::Offers)
545 } else if self.requires_token().is_some() {
546 Some(ComponentLineKind::Requires)
547 } else {
548 None
549 }
550 }
551}
552
553#[derive(Clone, Copy, PartialEq, Eq)]
558enum ScalarRole {
559 Min,
560 Max,
561 Step,
562 Pattern,
563}
564
565impl Constraint {
566 pub fn min(&self) -> Option<Literal> {
568 self.scalar(ScalarRole::Min)
569 }
570
571 pub fn max(&self) -> Option<Literal> {
573 self.scalar(ScalarRole::Max)
574 }
575
576 pub fn step(&self) -> Option<Literal> {
579 self.scalar(ScalarRole::Step)
580 }
581
582 pub fn match_pattern(&self) -> Option<Literal> {
585 self.scalar(ScalarRole::Pattern)
586 }
587
588 fn scalar(&self, wanted: ScalarRole) -> Option<Literal> {
593 let mut role = ScalarRole::Min;
594 for element in self.syntax().children_with_tokens() {
595 match element {
596 rowan::NodeOrToken::Token(token) => match token.kind() {
597 SyntaxKind::DotDot => role = ScalarRole::Max,
598 SyntaxKind::StepKw => role = ScalarRole::Step,
599 SyntaxKind::MatchKw => role = ScalarRole::Pattern,
600 _ => {}
601 },
602 rowan::NodeOrToken::Node(node) => {
603 if let Some(literal) = Literal::cast(node)
604 && role == wanted
605 {
606 return Some(literal);
607 }
608 }
609 }
610 }
611 None
612 }
613}
614
615impl ConstDef {
616 pub fn regex(&self) -> Option<SyntaxToken> {
619 self.value()?.regex_token()
620 }
621}
622
623impl StreamType {
624 pub fn element_primitive(&self) -> Option<SyntaxToken> {
628 self.syntax()
629 .children_with_tokens()
630 .filter_map(|element| element.into_token())
631 .find(|token| matches!(token.kind(), SyntaxKind::StringKw | SyntaxKind::BytesKw))
632 }
633}
634
635impl Timing {
636 pub fn duration(&self) -> Option<SyntaxToken> {
639 support::token(self.syntax(), SyntaxKind::Duration)
640 }
641}
642
643#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
646pub enum PredicateKind {
647 Require,
648 Ensure,
649}
650
651impl Attribute {
652 pub fn key_segments(&self) -> AstChildren<Name> {
657 support::children(self.syntax())
658 }
659
660 pub fn predicate_kind(&self) -> Option<PredicateKind> {
663 if self.require_token().is_some() {
664 Some(PredicateKind::Require)
665 } else if self.ensure_token().is_some() {
666 Some(PredicateKind::Ensure)
667 } else {
668 None
669 }
670 }
671}
672
673fn is_binary_op(kind: SyntaxKind) -> bool {
676 matches!(
677 kind,
678 SyntaxKind::PipePipe
679 | SyntaxKind::AmpAmp
680 | SyntaxKind::EqEq
681 | SyntaxKind::Neq
682 | SyntaxKind::Lt
683 | SyntaxKind::Le
684 | SyntaxKind::Gt
685 | SyntaxKind::Ge
686 | SyntaxKind::Plus
687 | SyntaxKind::Minus
688 | SyntaxKind::Star
689 | SyntaxKind::Slash
690 | SyntaxKind::Percent
691 )
692}
693
694impl BinaryExpr {
695 pub fn op_token(&self) -> Option<SyntaxToken> {
698 self.syntax()
699 .children_with_tokens()
700 .filter_map(|element| element.into_token())
701 .find(|token| is_binary_op(token.kind()))
702 }
703}
704
705impl PrefixExpr {
706 pub fn op_token(&self) -> Option<SyntaxToken> {
708 self.syntax()
709 .children_with_tokens()
710 .filter_map(|element| element.into_token())
711 .find(|token| matches!(token.kind(), SyntaxKind::Bang | SyntaxKind::Minus))
712 }
713}
714
715impl MemberExpr {
716 pub fn member_token(&self) -> Option<SyntaxToken> {
718 self.ident_token()
719 }
720}
721
722impl PathExpr {
723 pub fn name_token(&self) -> Option<SyntaxToken> {
725 self.ident_token()
726 }
727}
728
729impl LiteralExpr {
730 pub fn token(&self) -> Option<SyntaxToken> {
732 self.syntax()
733 .children_with_tokens()
734 .filter_map(|element| element.into_token())
735 .find(|token| !token.kind().is_trivia())
736 }
737}
738
739impl TimingRange {
740 pub fn min(&self) -> Option<SyntaxToken> {
742 self.endpoint(false)
743 }
744
745 pub fn max(&self) -> Option<SyntaxToken> {
747 self.endpoint(true)
748 }
749
750 fn endpoint(&self, after_dots: bool) -> Option<SyntaxToken> {
754 let mut seen_dots = false;
755 for element in self.syntax().children_with_tokens() {
756 let Some(token) = element.into_token() else {
757 continue;
758 };
759 match token.kind() {
760 SyntaxKind::DotDot => seen_dots = true,
761 SyntaxKind::Duration if seen_dots == after_dots => return Some(token),
762 _ => {}
763 }
764 }
765 None
766 }
767}
768
769#[cfg(test)]
770mod tests {
771 use rowan::GreenNodeBuilder;
772
773 use super::*;
774 use crate::syntax_kind::{SyntaxKind, SyntaxNode};
775
776 fn token(builder: &mut GreenNodeBuilder<'static>, kind: SyntaxKind, text: &str) {
777 builder.token(kind.into(), text);
778 }
779
780 fn literal(builder: &mut GreenNodeBuilder<'static>, kind: SyntaxKind, text: &str) {
781 builder.start_node(SyntaxKind::Literal.into());
782 token(builder, kind, text);
783 builder.finish_node();
784 }
785
786 fn name(builder: &mut GreenNodeBuilder<'static>, text: &str) {
787 builder.start_node(SyntaxKind::Name.into());
788 token(builder, SyntaxKind::Ident, text);
789 builder.finish_node();
790 }
791
792 fn path_type(builder: &mut GreenNodeBuilder<'static>, text: &str) {
793 builder.start_node(SyntaxKind::PathType.into());
794 builder.start_node(SyntaxKind::QualifiedName.into());
795 token(builder, SyntaxKind::Ident, text);
796 builder.finish_node();
797 builder.finish_node();
798 }
799
800 fn speed_source_file() -> SyntaxNode {
804 let mut b = GreenNodeBuilder::new();
805 b.start_node(SyntaxKind::SourceFile.into());
806 token(&mut b, SyntaxKind::DocComment, "/// Calibrated top speed.");
807 token(&mut b, SyntaxKind::Whitespace, "\n");
808 b.start_node(SyntaxKind::TypeDef.into());
809 token(&mut b, SyntaxKind::InternalKw, "internal");
810 token(&mut b, SyntaxKind::Whitespace, " ");
811 token(&mut b, SyntaxKind::TypeKw, "type");
812 token(&mut b, SyntaxKind::Whitespace, " ");
813 name(&mut b, "Speed");
814 token(&mut b, SyntaxKind::Colon, ":");
815 token(&mut b, SyntaxKind::Whitespace, " ");
816 b.start_node(SyntaxKind::UnitExpr.into());
817 token(&mut b, SyntaxKind::Ident, "km");
818 token(&mut b, SyntaxKind::Slash, "/");
819 token(&mut b, SyntaxKind::Ident, "h");
820 b.finish_node();
821 token(&mut b, SyntaxKind::Whitespace, " ");
822 b.start_node(SyntaxKind::Constraint.into());
823 token(&mut b, SyntaxKind::LBracket, "[");
824 literal(&mut b, SyntaxKind::FloatNumber, "0.0");
825 token(&mut b, SyntaxKind::DotDot, "..");
826 literal(&mut b, SyntaxKind::FloatNumber, "250.0");
827 token(&mut b, SyntaxKind::Whitespace, " ");
828 token(&mut b, SyntaxKind::StepKw, "step");
829 token(&mut b, SyntaxKind::Whitespace, " ");
830 literal(&mut b, SyntaxKind::FloatNumber, "0.5");
831 token(&mut b, SyntaxKind::RBracket, "]");
832 b.finish_node();
833 token(&mut b, SyntaxKind::Whitespace, " ");
834 b.start_node(SyntaxKind::InitValue.into());
835 token(&mut b, SyntaxKind::Eq, "=");
836 token(&mut b, SyntaxKind::Whitespace, " ");
837 literal(&mut b, SyntaxKind::FloatNumber, "0.0");
838 b.finish_node();
839 b.finish_node();
840 b.finish_node();
841 SyntaxNode::new_root(b.finish())
842 }
843
844 #[test]
845 fn type_def_cast_round_trips() {
846 let root = speed_source_file();
847 assert_eq!(
848 root.text().to_string(),
849 "/// Calibrated top speed.\ninternal type Speed: km/h [0.0..250.0 step 0.5] = 0.0",
850 );
851
852 let file = SourceFile::cast(root).expect("root is a SourceFile");
853 assert!(file.package_decl().is_none());
854 assert_eq!(file.imports().count(), 0);
855
856 let definitions: Vec<Definition> = file.definitions().collect();
857 assert_eq!(definitions.len(), 1);
858 let Definition::Type(type_def) = &definitions[0] else {
859 panic!("expected Definition::Type, got {:?}", definitions[0]);
860 };
861
862 let name = type_def.name().expect("TypeDef has a Name");
863 assert_eq!(
864 name.ident_token().expect("Name wraps an Ident").text(),
865 "Speed"
866 );
867
868 let Some(Backing::Unit(unit)) = type_def.backing() else {
869 panic!("expected a unit backing");
870 };
871 assert_eq!(unit.syntax().text().to_string(), "km/h");
872
873 let constraint = type_def.constraint().expect("TypeDef has a Constraint");
874 assert_eq!(constraint.min().unwrap().syntax().text().to_string(), "0.0");
875 assert_eq!(
876 constraint.max().unwrap().syntax().text().to_string(),
877 "250.0"
878 );
879 assert_eq!(
880 constraint.step().unwrap().syntax().text().to_string(),
881 "0.5"
882 );
883 assert!(constraint.match_pattern().is_none());
884 assert!(constraint.len().is_none());
885
886 let init = type_def.init_value().expect("TypeDef has an InitValue");
887 assert_eq!(init.literal().unwrap().syntax().text().to_string(), "0.0");
888
889 assert!(type_def.is_internal());
890 assert!(!type_def.is_error());
891
892 let docs = type_def.doc_comments();
893 assert_eq!(docs.len(), 1);
894 assert_eq!(docs[0].text(), "/// Calibrated top speed.");
895
896 let definition = &definitions[0];
898 assert_eq!(
899 definition.name().unwrap().ident_token().unwrap().text(),
900 "Speed",
901 );
902 assert!(definition.is_internal());
903 assert_eq!(definition.doc_comments().len(), 1);
904 }
905
906 fn driver_profile_source_file() -> SyntaxNode {
909 let mut b = GreenNodeBuilder::new();
910 b.start_node(SyntaxKind::SourceFile.into());
911 b.start_node(SyntaxKind::StructDef.into());
912 token(&mut b, SyntaxKind::StructKw, "struct");
913 token(&mut b, SyntaxKind::Whitespace, " ");
914 name(&mut b, "DriverProfile");
915 token(&mut b, SyntaxKind::Whitespace, " ");
916 token(&mut b, SyntaxKind::LBrace, "{");
917 token(&mut b, SyntaxKind::Whitespace, "\n ");
918 b.start_node(SyntaxKind::FieldDef.into());
919 name(&mut b, "name");
920 token(&mut b, SyntaxKind::Colon, ":");
921 token(&mut b, SyntaxKind::Whitespace, " ");
922 path_type(&mut b, "Name");
923 b.finish_node();
924 token(&mut b, SyntaxKind::Whitespace, "\n ");
925 b.start_node(SyntaxKind::ReservedEntry.into());
926 token(&mut b, SyntaxKind::ReservedKw, "reserved");
927 token(&mut b, SyntaxKind::Whitespace, " ");
928 name(&mut b, "legacyChecksum");
929 b.finish_node();
930 token(&mut b, SyntaxKind::Whitespace, "\n ");
931 b.start_node(SyntaxKind::FieldDef.into());
932 name(&mut b, "speed");
933 token(&mut b, SyntaxKind::Colon, ":");
934 token(&mut b, SyntaxKind::Whitespace, " ");
935 b.start_node(SyntaxKind::OptionalType.into());
936 path_type(&mut b, "Speed");
937 token(&mut b, SyntaxKind::Question, "?");
938 b.finish_node();
939 b.finish_node();
940 token(&mut b, SyntaxKind::Whitespace, "\n");
941 token(&mut b, SyntaxKind::RBrace, "}");
942 b.finish_node();
943 b.finish_node();
944 SyntaxNode::new_root(b.finish())
945 }
946
947 #[test]
948 fn struct_def_cast_round_trips() {
949 let root = driver_profile_source_file();
950 assert_eq!(
951 root.text().to_string(),
952 "struct DriverProfile {\n name: Name\n reserved legacyChecksum\n speed: Speed?\n}",
953 );
954
955 let file = SourceFile::cast(root).expect("root is a SourceFile");
956 let definitions: Vec<Definition> = file.definitions().collect();
957 assert_eq!(definitions.len(), 1);
958 let Definition::Struct(struct_def) = &definitions[0] else {
959 panic!("expected Definition::Struct, got {:?}", definitions[0]);
960 };
961
962 assert_eq!(
963 struct_def.name().unwrap().ident_token().unwrap().text(),
964 "DriverProfile",
965 );
966 assert!(!struct_def.is_internal());
967 assert!(!struct_def.is_error());
968
969 let members: Vec<StructMember> = struct_def.members().collect();
970 assert_eq!(members.len(), 3);
971
972 let StructMember::Field(name_field) = &members[0] else {
973 panic!("expected a field, got {:?}", members[0]);
974 };
975 assert_eq!(
976 name_field.name().unwrap().ident_token().unwrap().text(),
977 "name",
978 );
979 let Some(FieldType::Path(path)) = name_field.field_type() else {
980 panic!("expected a path field type");
981 };
982 assert_eq!(path.syntax().text().to_string(), "Name");
983 assert!(name_field.init_value().is_none());
984
985 let StructMember::Reserved(tombstone) = &members[1] else {
986 panic!("expected a reserved entry, got {:?}", members[1]);
987 };
988 assert_eq!(
989 tombstone.name().unwrap().ident_token().unwrap().text(),
990 "legacyChecksum",
991 );
992 assert!(tombstone.literal().is_none());
993
994 let StructMember::Field(speed_field) = &members[2] else {
995 panic!("expected a field, got {:?}", members[2]);
996 };
997 let Some(FieldType::Optional(optional)) = speed_field.field_type() else {
998 panic!("expected an optional field type");
999 };
1000 assert!(optional.question_token().is_some());
1001 let Some(FieldType::Path(inner)) = optional.field_type() else {
1002 panic!("expected a path inside the optional");
1003 };
1004 assert_eq!(inner.syntax().text().to_string(), "Speed");
1005 }
1006
1007 #[test]
1008 fn const_def_regex_accessor_finds_the_pattern() {
1009 let mut b = GreenNodeBuilder::new();
1010 b.start_node(SyntaxKind::SourceFile.into());
1011 b.start_node(SyntaxKind::ConstDef.into());
1012 token(&mut b, SyntaxKind::ConstKw, "const");
1013 token(&mut b, SyntaxKind::Whitespace, " ");
1014 name(&mut b, "SPEED_PATTERN");
1015 token(&mut b, SyntaxKind::Whitespace, " ");
1016 token(&mut b, SyntaxKind::Eq, "=");
1017 token(&mut b, SyntaxKind::Whitespace, " ");
1018 literal(&mut b, SyntaxKind::Regex, r"/^\d+(\.\d+)?$/");
1019 b.finish_node();
1020 b.finish_node();
1021 let root = SyntaxNode::new_root(b.finish());
1022
1023 let file = SourceFile::cast(root).expect("root is a SourceFile");
1024 let Some(Definition::Const(const_def)) = file.definitions().next() else {
1025 panic!("expected a const definition");
1026 };
1027 assert_eq!(
1028 const_def.name().unwrap().ident_token().unwrap().text(),
1029 "SPEED_PATTERN",
1030 );
1031 assert!(const_def.type_ref().is_none());
1032 assert_eq!(const_def.regex().unwrap().text(), r"/^\d+(\.\d+)?$/");
1033 }
1034
1035 #[test]
1039 fn misplaced_error_modifier_is_held_and_found() {
1040 let mut b = GreenNodeBuilder::new();
1041 b.start_node(SyntaxKind::SourceFile.into());
1042 b.start_node(SyntaxKind::TypeDef.into());
1043 token(&mut b, SyntaxKind::ErrorKw, "error");
1044 token(&mut b, SyntaxKind::Whitespace, " ");
1045 token(&mut b, SyntaxKind::TypeKw, "type");
1046 token(&mut b, SyntaxKind::Whitespace, " ");
1047 name(&mut b, "Broken");
1048 token(&mut b, SyntaxKind::Colon, ":");
1049 token(&mut b, SyntaxKind::Whitespace, " ");
1050 b.start_node(SyntaxKind::PrimitiveType.into());
1051 token(&mut b, SyntaxKind::BooleanKw, "boolean");
1052 b.finish_node();
1053 b.finish_node();
1054 b.finish_node();
1055 let root = SyntaxNode::new_root(b.finish());
1056
1057 let file = SourceFile::cast(root).expect("root is a SourceFile");
1058 let Some(Definition::Type(type_def)) = file.definitions().next() else {
1059 panic!("expected a type definition");
1060 };
1061 assert!(type_def.is_error());
1062 assert!(type_def.error_token().is_some());
1063 assert!(!type_def.is_internal());
1064 }
1065
1066 fn constraint(build: impl FnOnce(&mut GreenNodeBuilder<'static>)) -> Constraint {
1068 let mut b = GreenNodeBuilder::new();
1069 b.start_node(SyntaxKind::Constraint.into());
1070 token(&mut b, SyntaxKind::LBracket, "[");
1071 build(&mut b);
1072 token(&mut b, SyntaxKind::RBracket, "]");
1073 b.finish_node();
1074 Constraint::cast(SyntaxNode::new_root(b.finish())).expect("Constraint casts")
1075 }
1076
1077 fn bound(builder: &mut GreenNodeBuilder<'static>, min: &str, max: Option<&str>) {
1079 builder.start_node(SyntaxKind::Bound.into());
1080 literal(builder, SyntaxKind::IntNumber, min);
1081 if let Some(max) = max {
1082 token(builder, SyntaxKind::DotDot, "..");
1083 literal(builder, SyntaxKind::IntNumber, max);
1084 }
1085 builder.finish_node();
1086 }
1087
1088 #[test]
1091 fn open_ranges_anchor_the_scalar_roles() {
1092 let upper_only = constraint(|b| {
1094 token(b, SyntaxKind::DotDot, "..");
1095 literal(b, SyntaxKind::IntNumber, "255");
1096 });
1097 assert!(upper_only.min().is_none());
1098 assert_eq!(upper_only.max().unwrap().syntax().text().to_string(), "255");
1099 assert!(upper_only.step().is_none());
1100 assert!(upper_only.match_pattern().is_none());
1101 assert!(upper_only.len().is_none());
1102
1103 let lower_only = constraint(|b| {
1105 literal(b, SyntaxKind::IntNumber, "0");
1106 token(b, SyntaxKind::DotDot, "..");
1107 });
1108 assert_eq!(lower_only.min().unwrap().syntax().text().to_string(), "0");
1109 assert!(lower_only.max().is_none());
1110 assert!(lower_only.step().is_none());
1111 assert!(lower_only.match_pattern().is_none());
1112 }
1113
1114 #[test]
1116 fn bare_match_pattern_is_not_a_scalar_endpoint() {
1117 let bare_match = constraint(|b| {
1118 token(b, SyntaxKind::MatchKw, "match");
1119 token(b, SyntaxKind::Whitespace, " ");
1120 literal(b, SyntaxKind::Ident, "PATTERN");
1121 });
1122 assert!(bare_match.min().is_none());
1123 assert!(bare_match.max().is_none());
1124 assert!(bare_match.step().is_none());
1125 assert!(bare_match.len().is_none());
1126 assert_eq!(
1127 bare_match
1128 .match_pattern()
1129 .unwrap()
1130 .syntax()
1131 .text()
1132 .to_string(),
1133 "PATTERN",
1134 );
1135 }
1136
1137 #[test]
1141 fn length_bound_literals_do_not_leak_into_the_scalar_accessors() {
1142 let exact_len = constraint(|b| {
1144 bound(b, "17", None);
1145 token(b, SyntaxKind::Whitespace, " ");
1146 token(b, SyntaxKind::MatchKw, "match");
1147 token(b, SyntaxKind::Whitespace, " ");
1148 literal(b, SyntaxKind::Regex, "/re/");
1149 });
1150 assert!(exact_len.min().is_none());
1151 assert!(exact_len.max().is_none());
1152 let len = exact_len.len().expect("the length bound is present");
1153 assert_eq!(len.min().unwrap().syntax().text().to_string(), "17");
1154 assert!(len.max().is_none());
1155 assert_eq!(
1156 exact_len
1157 .match_pattern()
1158 .unwrap()
1159 .syntax()
1160 .text()
1161 .to_string(),
1162 "/re/",
1163 );
1164
1165 let len_range = constraint(|b| {
1167 bound(b, "3", Some("6"));
1168 token(b, SyntaxKind::Whitespace, " ");
1169 token(b, SyntaxKind::MatchKw, "match");
1170 token(b, SyntaxKind::Whitespace, " ");
1171 literal(b, SyntaxKind::Ident, "P");
1172 });
1173 assert!(len_range.min().is_none());
1174 assert!(len_range.max().is_none());
1175 let len = len_range.len().expect("the length bound is present");
1176 assert_eq!(len.min().unwrap().syntax().text().to_string(), "3");
1177 assert_eq!(len.max().unwrap().syntax().text().to_string(), "6");
1178 assert_eq!(
1179 len_range
1180 .match_pattern()
1181 .unwrap()
1182 .syntax()
1183 .text()
1184 .to_string(),
1185 "P",
1186 );
1187 }
1188
1189 #[test]
1192 fn attribute_and_expr_accessors_read_a_parsed_contract() {
1193 use crate::{Profile, parse};
1194
1195 let input = "package p\ninterface I {\n \
1196 command setGear(position: GearPosition) [\n \
1197 require position != GearPosition.PARK || currentSpeed == 0.0\n \
1198 someKey = 3\n ]\n}\n";
1199 let parsed = parse(input, Profile::Ridl);
1200 assert!(parsed.errors().is_empty(), "got: {:?}", parsed.errors());
1201
1202 let attr_block = parsed
1203 .syntax()
1204 .descendants()
1205 .find_map(AttrBlock::cast)
1206 .expect("an AttrBlock parses");
1207 let attributes: Vec<Attribute> = attr_block.attributes().collect();
1208 assert_eq!(attributes.len(), 2);
1209
1210 let predicate = &attributes[0];
1212 assert_eq!(predicate.predicate_kind(), Some(PredicateKind::Require));
1213 assert!(predicate.key().is_none());
1214 assert!(predicate.value().is_none());
1215 let Some(Expr::Binary(or)) = predicate.expr() else {
1216 panic!("expected a binary root, got {:?}", predicate.expr());
1217 };
1218 assert_eq!(or.op_token().unwrap().text(), "||");
1219 let Some(Expr::Binary(neq)) = or.lhs() else {
1220 panic!("expected a binary lhs, got {:?}", or.lhs());
1221 };
1222 assert_eq!(neq.op_token().unwrap().text(), "!=");
1223 let Some(Expr::Member(member)) = neq.rhs() else {
1224 panic!("expected a member rhs, got {:?}", neq.rhs());
1225 };
1226 assert_eq!(member.member_token().unwrap().text(), "PARK");
1227 let Some(Expr::Path(base)) = member.base() else {
1228 panic!("expected a path base, got {:?}", member.base());
1229 };
1230 assert_eq!(base.name_token().unwrap().text(), "GearPosition");
1231 let Some(Expr::Binary(eq)) = or.rhs() else {
1232 panic!("expected a binary rhs, got {:?}", or.rhs());
1233 };
1234 let Some(Expr::Literal(zero)) = eq.rhs() else {
1235 panic!("expected a literal rhs, got {:?}", eq.rhs());
1236 };
1237 assert_eq!(zero.token().unwrap().text(), "0.0");
1238
1239 let assignment = &attributes[1];
1241 assert!(assignment.predicate_kind().is_none());
1242 assert!(assignment.expr().is_none());
1243 assert_eq!(
1244 assignment.key().unwrap().ident_token().unwrap().text(),
1245 "someKey",
1246 );
1247 let value = assignment.value().expect("the assignment has a value");
1248 assert_eq!(value.literal().unwrap().syntax().text().to_string(), "3");
1249 }
1250
1251 const THREE_SHAPES: &str = "\
1254package app
1255
1256interface VehicleStatus {
1257 signal speed : Speed @10ms
1258 event doorOpened : DoorState
1259}
1260
1261service veh.adas.status : VehicleStatus
1262
1263service veh.adas.logs {
1264 signal level : Level @1s
1265 reserved oldTail
1266 query tailLogs(filter: Filter): Line
1267}
1268";
1269
1270 #[test]
1275 fn shapes_walks_interfaces_and_inline_service_bodies() {
1276 use crate::{Profile, parse};
1277
1278 let parsed = parse(THREE_SHAPES, Profile::Ridl);
1279 assert!(parsed.errors().is_empty(), "got: {:?}", parsed.errors());
1280 let file = SourceFile::cast(parsed.syntax()).expect("the root is a SourceFile");
1281
1282 let walk: Vec<(String, usize)> = file
1283 .shapes()
1284 .map(|shape| {
1285 (
1286 shape.identity().expect("every shape here is named"),
1287 shape.members().count(),
1288 )
1289 })
1290 .collect();
1291 assert_eq!(
1292 walk,
1293 [
1294 ("VehicleStatus".to_string(), 2),
1295 ("veh.adas.logs".to_string(), 3),
1296 ],
1297 "the interface, then the inline shape under the service's dotted \
1298 name; the reference-form service contributes nothing",
1299 );
1300
1301 assert_eq!(file.services().count(), 2);
1303 assert!(
1304 !file
1305 .shapes()
1306 .any(|shape| shape.identity().as_deref() == Some("veh.adas.status")),
1307 );
1308 }
1309
1310 #[test]
1313 fn shape_identity_range_covers_the_declared_name() {
1314 use crate::{Profile, parse};
1315
1316 let parsed = parse(THREE_SHAPES, Profile::Ridl);
1317 let file = SourceFile::cast(parsed.syntax()).expect("the root is a SourceFile");
1318
1319 for shape in file.shapes() {
1320 let name = shape.identity().expect("named");
1321 let range = shape.identity_range().expect("named");
1322 let start = usize::from(range.start());
1323 let end = usize::from(range.end());
1324 assert_eq!(
1325 &THREE_SHAPES[start..end],
1326 name,
1327 "the range must cover exactly the declared name",
1328 );
1329 }
1330 }
1331
1332 #[test]
1337 fn a_service_with_neither_form_is_an_empty_inline_shape() {
1338 use crate::{Profile, parse};
1339
1340 let parsed = parse("package app\nservice veh.adas.broken\n", Profile::Ridl);
1341 let file = SourceFile::cast(parsed.syntax()).expect("the root is a SourceFile");
1342
1343 let shapes: Vec<InterfaceShape> = file.shapes().collect();
1344 assert_eq!(shapes.len(), 1);
1345 assert_eq!(shapes[0].identity().as_deref(), Some("veh.adas.broken"));
1346 assert_eq!(shapes[0].members().count(), 0);
1347 }
1348
1349 #[test]
1352 fn dotted_name_text_joins_the_segments() {
1353 use crate::{Profile, parse};
1354
1355 let parsed = parse(
1356 "package app\nservice veh . adas . logs {\n}\n",
1357 Profile::Ridl,
1358 );
1359 let file = SourceFile::cast(parsed.syntax()).expect("the root is a SourceFile");
1360 let service = file.services().next().expect("a service parses");
1361 assert_eq!(service.name().expect("named").text(), "veh.adas.logs");
1362 }
1363
1364 #[test]
1365 fn enum_set_def_derived_form_exposes_the_backing_ref() {
1366 let mut b = GreenNodeBuilder::new();
1367 b.start_node(SyntaxKind::SourceFile.into());
1368 b.start_node(SyntaxKind::EnumSetDef.into());
1369 token(&mut b, SyntaxKind::EnumsetKw, "enumset");
1370 token(&mut b, SyntaxKind::Whitespace, " ");
1371 name(&mut b, "WarningFlags");
1372 token(&mut b, SyntaxKind::Colon, ":");
1373 token(&mut b, SyntaxKind::Whitespace, " ");
1374 b.start_node(SyntaxKind::PathType.into());
1375 b.start_node(SyntaxKind::QualifiedName.into());
1376 token(&mut b, SyntaxKind::Ident, "Warning");
1377 b.finish_node();
1378 b.finish_node();
1379 b.finish_node();
1380 b.finish_node();
1381 let root = SyntaxNode::new_root(b.finish());
1382
1383 let file = SourceFile::cast(root).expect("root is a SourceFile");
1384 let Some(Definition::EnumSet(enum_set)) = file.definitions().next() else {
1385 panic!("expected an enumset definition");
1386 };
1387 assert_eq!(
1388 enum_set.name().unwrap().ident_token().unwrap().text(),
1389 "WarningFlags",
1390 );
1391 assert_eq!(
1392 enum_set.backing_ref().unwrap().syntax().text().to_string(),
1393 "Warning",
1394 );
1395 assert_eq!(enum_set.bits().count(), 0);
1396 }
1397}