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 fn is_open(&self) -> bool {
345 support::token(self.syntax(), SyntaxKind::OpenKw).is_some()
346 }
347}
348
349pub trait HasDocComments: AstNode {
352 fn doc_comments(&self) -> Vec<SyntaxToken> {
355 doc_comments_before(self.syntax())
356 }
357}
358
359pub fn doc_comments_before(node: &SyntaxNode) -> Vec<SyntaxToken> {
363 let mut out = Vec::new();
364 let mut cursor = node.prev_sibling_or_token();
365 while let Some(rowan::NodeOrToken::Token(token)) = cursor {
366 if !token.kind().is_trivia() {
367 break;
368 }
369 if token.kind() == SyntaxKind::DocComment {
370 out.push(token.clone());
371 }
372 cursor = token.prev_sibling_or_token();
373 }
374 out.reverse();
375 out
376}
377
378pub fn is_doc_carrier(kind: SyntaxKind) -> bool {
382 matches!(
383 kind,
384 SyntaxKind::TypeDef
385 | SyntaxKind::ConstDef
386 | SyntaxKind::StructDef
387 | SyntaxKind::EnumDef
388 | SyntaxKind::EnumSetDef
389 | SyntaxKind::UnionDef
390 | SyntaxKind::FieldDef
391 | SyntaxKind::ReservedEntry
392 | SyntaxKind::EnumValue
393 | SyntaxKind::EnumSetBit
394 | SyntaxKind::UnionArm
395 | SyntaxKind::InterfaceDef
396 | SyntaxKind::ServiceDef
397 | SyntaxKind::SignalDef
398 | SyntaxKind::EventDef
399 | SyntaxKind::CommandDef
400 | SyntaxKind::QueryDef
401 | SyntaxKind::FixedDef
402 | SyntaxKind::Param
403 | SyntaxKind::SystemDef
404 | SyntaxKind::ComponentDef
405 | SyntaxKind::ComponentLine
406 | SyntaxKind::DistributionDef
407 | SyntaxKind::DeploymentDef
408 | SyntaxKind::MachineDef
409 | SyntaxKind::MemberLine
410 )
411}
412
413impl HasName for TypeDef {}
414impl HasName for ConstDef {}
415impl HasName for StructDef {}
416impl HasName for EnumDef {}
417impl HasName for EnumSetDef {}
418impl HasName for UnionDef {}
419impl HasName for Definition {}
420impl HasName for InterfaceDef {}
421impl HasName for SignalDef {}
422impl HasName for EventDef {}
423impl HasName for CommandDef {}
424impl HasName for QueryDef {}
425impl HasName for FixedDef {}
426impl HasName for InterfaceMember {}
427
428impl HasName for SystemDef {}
429impl HasName for ComponentDef {}
430impl HasName for DistributionDef {}
431impl HasName for DeploymentDef {}
432impl HasName for MachineDef {}
433
434impl HasModifiers for TypeDef {}
435impl HasModifiers for ConstDef {}
436impl HasModifiers for StructDef {}
437impl HasModifiers for EnumDef {}
438impl HasModifiers for EnumSetDef {}
439impl HasModifiers for UnionDef {}
440impl HasModifiers for Definition {}
441impl HasModifiers for InterfaceDef {}
442
443impl HasDocComments for TypeDef {}
444impl HasDocComments for ConstDef {}
445impl HasDocComments for StructDef {}
446impl HasDocComments for EnumDef {}
447impl HasDocComments for EnumSetDef {}
448impl HasDocComments for UnionDef {}
449impl HasDocComments for Definition {}
450impl HasDocComments for InterfaceDef {}
451impl HasDocComments for SignalDef {}
452impl HasDocComments for EventDef {}
453impl HasDocComments for CommandDef {}
454impl HasDocComments for QueryDef {}
455impl HasDocComments for FixedDef {}
456impl HasDocComments for InterfaceMember {}
457impl HasDocComments for ServiceDef {}
458impl HasDocComments for FieldDef {}
461impl HasDocComments for ReservedEntry {}
462impl HasDocComments for EnumValue {}
463impl HasDocComments for EnumSetBit {}
464impl HasDocComments for UnionArm {}
465impl HasDocComments for Param {}
466
467impl HasDocComments for SystemDef {}
468impl HasDocComments for ComponentDef {}
469impl HasDocComments for ComponentLine {}
470impl HasDocComments for DistributionDef {}
471impl HasDocComments for DeploymentDef {}
472impl HasDocComments for MachineDef {}
473impl HasDocComments for MemberLine {}
474
475impl DottedName {
476 pub fn segments(&self) -> impl Iterator<Item = SyntaxToken> + '_ {
481 self.syntax()
482 .children_with_tokens()
483 .filter_map(|element| element.into_token())
484 .filter(|token| token.kind() == SyntaxKind::Ident)
485 }
486
487 pub fn text(&self) -> String {
491 self.syntax()
492 .children_with_tokens()
493 .filter_map(|element| element.into_token())
494 .filter(|token| !token.kind().is_trivia())
495 .map(|token| token.text().to_string())
496 .collect()
497 }
498}
499
500#[derive(Debug, Clone, PartialEq, Eq, Hash)]
513pub enum InterfaceShape {
514 Interface(InterfaceDef),
516 ServiceInline(ServiceDef),
518}
519
520impl InterfaceShape {
521 pub fn identity(&self) -> Option<String> {
526 match self {
527 Self::Interface(def) => Some(def.name()?.ident_token()?.text().to_string()),
528 Self::ServiceInline(def) => {
529 let text = def.name()?.text();
530 (!text.is_empty()).then_some(text)
531 }
532 }
533 }
534
535 pub fn identity_range(&self) -> Option<TextRange> {
539 match self {
540 Self::Interface(def) => Some(def.name()?.syntax().text_range()),
541 Self::ServiceInline(def) => Some(def.name()?.syntax().text_range()),
542 }
543 }
544
545 pub fn members(&self) -> AstChildren<InterfaceMember> {
548 match self {
549 Self::Interface(def) => def.members(),
550 Self::ServiceInline(def) => def.inline_members(),
551 }
552 }
553
554 pub fn syntax(&self) -> &SyntaxNode {
556 match self {
557 Self::Interface(def) => def.syntax(),
558 Self::ServiceInline(def) => def.syntax(),
559 }
560 }
561}
562
563impl SourceFile {
564 pub fn shapes(&self) -> impl Iterator<Item = InterfaceShape> + '_ {
576 self.interfaces().map(InterfaceShape::Interface).chain(
577 self.services()
578 .filter(|service| service.colon_token().is_none())
579 .map(InterfaceShape::ServiceInline),
580 )
581 }
582}
583
584#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
588pub enum ComponentLineKind {
589 Offers,
591 Requires,
593}
594
595impl ComponentLine {
596 pub fn kind(&self) -> Option<ComponentLineKind> {
599 if self.offers_token().is_some() {
600 Some(ComponentLineKind::Offers)
601 } else if self.requires_token().is_some() {
602 Some(ComponentLineKind::Requires)
603 } else {
604 None
605 }
606 }
607}
608
609#[derive(Clone, Copy, PartialEq, Eq)]
614enum ScalarRole {
615 Min,
616 Max,
617 Step,
618 Pattern,
619}
620
621impl Constraint {
622 pub fn min(&self) -> Option<Literal> {
624 self.scalar(ScalarRole::Min)
625 }
626
627 pub fn max(&self) -> Option<Literal> {
629 self.scalar(ScalarRole::Max)
630 }
631
632 pub fn step(&self) -> Option<Literal> {
635 self.scalar(ScalarRole::Step)
636 }
637
638 pub fn match_pattern(&self) -> Option<Literal> {
641 self.scalar(ScalarRole::Pattern)
642 }
643
644 fn scalar(&self, wanted: ScalarRole) -> Option<Literal> {
649 let mut role = ScalarRole::Min;
650 for element in self.syntax().children_with_tokens() {
651 match element {
652 rowan::NodeOrToken::Token(token) => match token.kind() {
653 SyntaxKind::DotDot => role = ScalarRole::Max,
654 SyntaxKind::StepKw => role = ScalarRole::Step,
655 SyntaxKind::MatchKw => role = ScalarRole::Pattern,
656 _ => {}
657 },
658 rowan::NodeOrToken::Node(node) => {
659 if let Some(literal) = Literal::cast(node)
660 && role == wanted
661 {
662 return Some(literal);
663 }
664 }
665 }
666 }
667 None
668 }
669}
670
671impl ConstDef {
672 pub fn regex(&self) -> Option<SyntaxToken> {
675 self.value()?.regex_token()
676 }
677}
678
679impl StreamType {
680 pub fn element_primitive(&self) -> Option<SyntaxToken> {
684 self.syntax()
685 .children_with_tokens()
686 .filter_map(|element| element.into_token())
687 .find(|token| matches!(token.kind(), SyntaxKind::StringKw | SyntaxKind::BytesKw))
688 }
689}
690
691impl Timing {
692 pub fn duration(&self) -> Option<SyntaxToken> {
695 support::token(self.syntax(), SyntaxKind::Duration)
696 }
697}
698
699#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
702pub enum PredicateKind {
703 Require,
704 Ensure,
705}
706
707impl Attribute {
708 pub fn key_segments(&self) -> AstChildren<Name> {
713 support::children(self.syntax())
714 }
715
716 pub fn predicate_kind(&self) -> Option<PredicateKind> {
719 if self.require_token().is_some() {
720 Some(PredicateKind::Require)
721 } else if self.ensure_token().is_some() {
722 Some(PredicateKind::Ensure)
723 } else {
724 None
725 }
726 }
727}
728
729fn is_binary_op(kind: SyntaxKind) -> bool {
732 matches!(
733 kind,
734 SyntaxKind::PipePipe
735 | SyntaxKind::AmpAmp
736 | SyntaxKind::EqEq
737 | SyntaxKind::Neq
738 | SyntaxKind::Lt
739 | SyntaxKind::Le
740 | SyntaxKind::Gt
741 | SyntaxKind::Ge
742 | SyntaxKind::Plus
743 | SyntaxKind::Minus
744 | SyntaxKind::Star
745 | SyntaxKind::Slash
746 | SyntaxKind::Percent
747 )
748}
749
750impl BinaryExpr {
751 pub fn op_token(&self) -> Option<SyntaxToken> {
754 self.syntax()
755 .children_with_tokens()
756 .filter_map(|element| element.into_token())
757 .find(|token| is_binary_op(token.kind()))
758 }
759}
760
761impl PrefixExpr {
762 pub fn op_token(&self) -> Option<SyntaxToken> {
764 self.syntax()
765 .children_with_tokens()
766 .filter_map(|element| element.into_token())
767 .find(|token| matches!(token.kind(), SyntaxKind::Bang | SyntaxKind::Minus))
768 }
769}
770
771impl MemberExpr {
772 pub fn member_token(&self) -> Option<SyntaxToken> {
774 self.ident_token()
775 }
776}
777
778impl PathExpr {
779 pub fn name_token(&self) -> Option<SyntaxToken> {
781 self.ident_token()
782 }
783}
784
785impl LiteralExpr {
786 pub fn token(&self) -> Option<SyntaxToken> {
788 self.syntax()
789 .children_with_tokens()
790 .filter_map(|element| element.into_token())
791 .find(|token| !token.kind().is_trivia())
792 }
793}
794
795impl TimingRange {
796 pub fn min(&self) -> Option<SyntaxToken> {
798 self.endpoint(false)
799 }
800
801 pub fn max(&self) -> Option<SyntaxToken> {
803 self.endpoint(true)
804 }
805
806 fn endpoint(&self, after_dots: bool) -> Option<SyntaxToken> {
810 let mut seen_dots = false;
811 for element in self.syntax().children_with_tokens() {
812 let Some(token) = element.into_token() else {
813 continue;
814 };
815 match token.kind() {
816 SyntaxKind::DotDot => seen_dots = true,
817 SyntaxKind::Duration if seen_dots == after_dots => return Some(token),
818 _ => {}
819 }
820 }
821 None
822 }
823}
824
825#[cfg(test)]
826mod tests {
827 use rowan::GreenNodeBuilder;
828
829 use super::*;
830 use crate::syntax_kind::{SyntaxKind, SyntaxNode};
831
832 fn token(builder: &mut GreenNodeBuilder<'static>, kind: SyntaxKind, text: &str) {
833 builder.token(kind.into(), text);
834 }
835
836 fn literal(builder: &mut GreenNodeBuilder<'static>, kind: SyntaxKind, text: &str) {
837 builder.start_node(SyntaxKind::Literal.into());
838 token(builder, kind, text);
839 builder.finish_node();
840 }
841
842 fn name(builder: &mut GreenNodeBuilder<'static>, text: &str) {
843 builder.start_node(SyntaxKind::Name.into());
844 token(builder, SyntaxKind::Ident, text);
845 builder.finish_node();
846 }
847
848 fn path_type(builder: &mut GreenNodeBuilder<'static>, text: &str) {
849 builder.start_node(SyntaxKind::PathType.into());
850 builder.start_node(SyntaxKind::QualifiedName.into());
851 token(builder, SyntaxKind::Ident, text);
852 builder.finish_node();
853 builder.finish_node();
854 }
855
856 fn speed_source_file() -> SyntaxNode {
859 let mut b = GreenNodeBuilder::new();
860 b.start_node(SyntaxKind::SourceFile.into());
861 token(&mut b, SyntaxKind::DocComment, "/// Calibrated top speed.");
862 token(&mut b, SyntaxKind::Whitespace, "\n");
863 b.start_node(SyntaxKind::TypeDef.into());
864 token(&mut b, SyntaxKind::InternalKw, "internal");
865 token(&mut b, SyntaxKind::Whitespace, " ");
866 token(&mut b, SyntaxKind::TypeKw, "type");
867 token(&mut b, SyntaxKind::Whitespace, " ");
868 name(&mut b, "Speed");
869 token(&mut b, SyntaxKind::Colon, ":");
870 token(&mut b, SyntaxKind::Whitespace, " ");
871 b.start_node(SyntaxKind::UnitExpr.into());
872 token(&mut b, SyntaxKind::Ident, "km");
873 token(&mut b, SyntaxKind::Slash, "/");
874 token(&mut b, SyntaxKind::Ident, "h");
875 b.finish_node();
876 token(&mut b, SyntaxKind::Whitespace, " ");
877 b.start_node(SyntaxKind::Constraint.into());
878 token(&mut b, SyntaxKind::LBracket, "[");
879 literal(&mut b, SyntaxKind::FloatNumber, "0.0");
880 token(&mut b, SyntaxKind::DotDot, "..");
881 literal(&mut b, SyntaxKind::FloatNumber, "250.0");
882 token(&mut b, SyntaxKind::Whitespace, " ");
883 token(&mut b, SyntaxKind::StepKw, "step");
884 token(&mut b, SyntaxKind::Whitespace, " ");
885 literal(&mut b, SyntaxKind::FloatNumber, "0.5");
886 token(&mut b, SyntaxKind::RBracket, "]");
887 b.finish_node();
888 token(&mut b, SyntaxKind::Whitespace, " ");
889 b.start_node(SyntaxKind::InitValue.into());
890 token(&mut b, SyntaxKind::Eq, "=");
891 token(&mut b, SyntaxKind::Whitespace, " ");
892 literal(&mut b, SyntaxKind::FloatNumber, "0.0");
893 b.finish_node();
894 b.finish_node();
895 b.finish_node();
896 SyntaxNode::new_root(b.finish())
897 }
898
899 #[test]
903 fn member_doc_comments_attach_to_their_nodes() {
904 let text = "package app\n\
905 struct S {\n /// Doc 1.\n a: boolean\n /// Doc 2.\n reserved b\n}\n\
906 enum E {\n /// Doc 3.\n A = 1\n}\n\
907 enumset F {\n /// Doc 4.\n X = 0\n}\n\
908 union U {\n /// Doc 5.\n a: S\n}\n\
909 interface I {\n command c(\n /// Doc 6.\n a: S\n ) @[..50ms]\n}\n";
910 let parse = crate::parse(text, crate::Profile::Ridl);
911 assert_eq!(parse.errors(), &[], "the fixture parses");
912 let root = parse.syntax();
913 let docs_of = |kind: SyntaxKind| -> Vec<String> {
914 root.descendants()
915 .filter(|node| node.kind() == kind)
916 .flat_map(|node| doc_comments_before(&node))
917 .map(|token| token.text().to_string())
918 .collect()
919 };
920 assert_eq!(docs_of(SyntaxKind::FieldDef), ["/// Doc 1."]);
921 assert_eq!(docs_of(SyntaxKind::ReservedEntry), ["/// Doc 2."]);
922 assert_eq!(docs_of(SyntaxKind::EnumValue), ["/// Doc 3."]);
923 assert_eq!(docs_of(SyntaxKind::EnumSetBit), ["/// Doc 4."]);
924 assert_eq!(docs_of(SyntaxKind::UnionArm), ["/// Doc 5."]);
925 assert_eq!(docs_of(SyntaxKind::Param), ["/// Doc 6."]);
926 let param = root
927 .descendants()
928 .find_map(Param::cast)
929 .expect("the fixture has a parameter");
930 assert_eq!(
931 param.doc_comments().len(),
932 1,
933 "the trait reads the same run"
934 );
935 }
936
937 #[test]
938 fn type_def_cast_round_trips() {
939 let root = speed_source_file();
940 assert_eq!(
941 root.text().to_string(),
942 "/// Calibrated top speed.\ninternal type Speed: km/h [0.0..250.0 step 0.5] = 0.0",
943 );
944
945 let file = SourceFile::cast(root).expect("root is a SourceFile");
946 assert!(file.package_decl().is_none());
947 assert_eq!(file.imports().count(), 0);
948
949 let definitions: Vec<Definition> = file.definitions().collect();
950 assert_eq!(definitions.len(), 1);
951 let Definition::Type(type_def) = &definitions[0] else {
952 panic!("expected Definition::Type, got {:?}", definitions[0]);
953 };
954
955 let name = type_def.name().expect("TypeDef has a Name");
956 assert_eq!(
957 name.ident_token().expect("Name wraps an Ident").text(),
958 "Speed"
959 );
960
961 let Some(Backing::Unit(unit)) = type_def.backing() else {
962 panic!("expected a unit backing");
963 };
964 assert_eq!(unit.syntax().text().to_string(), "km/h");
965
966 let constraint = type_def.constraint().expect("TypeDef has a Constraint");
967 assert_eq!(constraint.min().unwrap().syntax().text().to_string(), "0.0");
968 assert_eq!(
969 constraint.max().unwrap().syntax().text().to_string(),
970 "250.0"
971 );
972 assert_eq!(
973 constraint.step().unwrap().syntax().text().to_string(),
974 "0.5"
975 );
976 assert!(constraint.match_pattern().is_none());
977 assert!(constraint.len().is_none());
978
979 let init = type_def.init_value().expect("TypeDef has an InitValue");
980 assert_eq!(init.literal().unwrap().syntax().text().to_string(), "0.0");
981
982 assert!(type_def.is_internal());
983 assert!(!type_def.is_error());
984
985 let docs = type_def.doc_comments();
986 assert_eq!(docs.len(), 1);
987 assert_eq!(docs[0].text(), "/// Calibrated top speed.");
988
989 let definition = &definitions[0];
991 assert_eq!(
992 definition.name().unwrap().ident_token().unwrap().text(),
993 "Speed",
994 );
995 assert!(definition.is_internal());
996 assert_eq!(definition.doc_comments().len(), 1);
997 }
998
999 fn driver_profile_source_file() -> SyntaxNode {
1002 let mut b = GreenNodeBuilder::new();
1003 b.start_node(SyntaxKind::SourceFile.into());
1004 b.start_node(SyntaxKind::StructDef.into());
1005 token(&mut b, SyntaxKind::StructKw, "struct");
1006 token(&mut b, SyntaxKind::Whitespace, " ");
1007 name(&mut b, "DriverProfile");
1008 token(&mut b, SyntaxKind::Whitespace, " ");
1009 token(&mut b, SyntaxKind::LBrace, "{");
1010 token(&mut b, SyntaxKind::Whitespace, "\n ");
1011 b.start_node(SyntaxKind::FieldDef.into());
1012 name(&mut b, "name");
1013 token(&mut b, SyntaxKind::Colon, ":");
1014 token(&mut b, SyntaxKind::Whitespace, " ");
1015 path_type(&mut b, "Name");
1016 b.finish_node();
1017 token(&mut b, SyntaxKind::Whitespace, "\n ");
1018 b.start_node(SyntaxKind::ReservedEntry.into());
1019 token(&mut b, SyntaxKind::ReservedKw, "reserved");
1020 token(&mut b, SyntaxKind::Whitespace, " ");
1021 name(&mut b, "legacyChecksum");
1022 b.finish_node();
1023 token(&mut b, SyntaxKind::Whitespace, "\n ");
1024 b.start_node(SyntaxKind::FieldDef.into());
1025 name(&mut b, "speed");
1026 token(&mut b, SyntaxKind::Colon, ":");
1027 token(&mut b, SyntaxKind::Whitespace, " ");
1028 b.start_node(SyntaxKind::OptionalType.into());
1029 path_type(&mut b, "Speed");
1030 token(&mut b, SyntaxKind::Question, "?");
1031 b.finish_node();
1032 b.finish_node();
1033 token(&mut b, SyntaxKind::Whitespace, "\n");
1034 token(&mut b, SyntaxKind::RBrace, "}");
1035 b.finish_node();
1036 b.finish_node();
1037 SyntaxNode::new_root(b.finish())
1038 }
1039
1040 #[test]
1041 fn struct_def_cast_round_trips() {
1042 let root = driver_profile_source_file();
1043 assert_eq!(
1044 root.text().to_string(),
1045 "struct DriverProfile {\n name: Name\n reserved legacyChecksum\n speed: Speed?\n}",
1046 );
1047
1048 let file = SourceFile::cast(root).expect("root is a SourceFile");
1049 let definitions: Vec<Definition> = file.definitions().collect();
1050 assert_eq!(definitions.len(), 1);
1051 let Definition::Struct(struct_def) = &definitions[0] else {
1052 panic!("expected Definition::Struct, got {:?}", definitions[0]);
1053 };
1054
1055 assert_eq!(
1056 struct_def.name().unwrap().ident_token().unwrap().text(),
1057 "DriverProfile",
1058 );
1059 assert!(!struct_def.is_internal());
1060 assert!(!struct_def.is_error());
1061
1062 let members: Vec<StructMember> = struct_def.members().collect();
1063 assert_eq!(members.len(), 3);
1064
1065 let StructMember::Field(name_field) = &members[0] else {
1066 panic!("expected a field, got {:?}", members[0]);
1067 };
1068 assert_eq!(
1069 name_field.name().unwrap().ident_token().unwrap().text(),
1070 "name",
1071 );
1072 let Some(FieldType::Path(path)) = name_field.field_type() else {
1073 panic!("expected a path field type");
1074 };
1075 assert_eq!(path.syntax().text().to_string(), "Name");
1076 assert!(name_field.init_value().is_none());
1077
1078 let StructMember::Reserved(tombstone) = &members[1] else {
1079 panic!("expected a reserved entry, got {:?}", members[1]);
1080 };
1081 assert_eq!(
1082 tombstone.name().unwrap().ident_token().unwrap().text(),
1083 "legacyChecksum",
1084 );
1085 assert!(tombstone.literal().is_none());
1086
1087 let StructMember::Field(speed_field) = &members[2] else {
1088 panic!("expected a field, got {:?}", members[2]);
1089 };
1090 let Some(FieldType::Optional(optional)) = speed_field.field_type() else {
1091 panic!("expected an optional field type");
1092 };
1093 assert!(optional.question_token().is_some());
1094 let Some(FieldType::Path(inner)) = optional.field_type() else {
1095 panic!("expected a path inside the optional");
1096 };
1097 assert_eq!(inner.syntax().text().to_string(), "Speed");
1098 }
1099
1100 #[test]
1101 fn const_def_regex_accessor_finds_the_pattern() {
1102 let mut b = GreenNodeBuilder::new();
1103 b.start_node(SyntaxKind::SourceFile.into());
1104 b.start_node(SyntaxKind::ConstDef.into());
1105 token(&mut b, SyntaxKind::ConstKw, "const");
1106 token(&mut b, SyntaxKind::Whitespace, " ");
1107 name(&mut b, "SPEED_PATTERN");
1108 token(&mut b, SyntaxKind::Whitespace, " ");
1109 token(&mut b, SyntaxKind::Eq, "=");
1110 token(&mut b, SyntaxKind::Whitespace, " ");
1111 literal(&mut b, SyntaxKind::Regex, r"/^\d+(\.\d+)?$/");
1112 b.finish_node();
1113 b.finish_node();
1114 let root = SyntaxNode::new_root(b.finish());
1115
1116 let file = SourceFile::cast(root).expect("root is a SourceFile");
1117 let Some(Definition::Const(const_def)) = file.definitions().next() else {
1118 panic!("expected a const definition");
1119 };
1120 assert_eq!(
1121 const_def.name().unwrap().ident_token().unwrap().text(),
1122 "SPEED_PATTERN",
1123 );
1124 assert!(const_def.type_ref().is_none());
1125 assert_eq!(const_def.regex().unwrap().text(), r"/^\d+(\.\d+)?$/");
1126 }
1127
1128 #[test]
1132 fn misplaced_error_modifier_is_held_and_found() {
1133 let mut b = GreenNodeBuilder::new();
1134 b.start_node(SyntaxKind::SourceFile.into());
1135 b.start_node(SyntaxKind::TypeDef.into());
1136 token(&mut b, SyntaxKind::ErrorKw, "error");
1137 token(&mut b, SyntaxKind::Whitespace, " ");
1138 token(&mut b, SyntaxKind::TypeKw, "type");
1139 token(&mut b, SyntaxKind::Whitespace, " ");
1140 name(&mut b, "Broken");
1141 token(&mut b, SyntaxKind::Colon, ":");
1142 token(&mut b, SyntaxKind::Whitespace, " ");
1143 b.start_node(SyntaxKind::PrimitiveType.into());
1144 token(&mut b, SyntaxKind::BooleanKw, "boolean");
1145 b.finish_node();
1146 b.finish_node();
1147 b.finish_node();
1148 let root = SyntaxNode::new_root(b.finish());
1149
1150 let file = SourceFile::cast(root).expect("root is a SourceFile");
1151 let Some(Definition::Type(type_def)) = file.definitions().next() else {
1152 panic!("expected a type definition");
1153 };
1154 assert!(type_def.is_error());
1155 assert!(type_def.error_token().is_some());
1156 assert!(!type_def.is_internal());
1157 }
1158
1159 fn constraint(build: impl FnOnce(&mut GreenNodeBuilder<'static>)) -> Constraint {
1161 let mut b = GreenNodeBuilder::new();
1162 b.start_node(SyntaxKind::Constraint.into());
1163 token(&mut b, SyntaxKind::LBracket, "[");
1164 build(&mut b);
1165 token(&mut b, SyntaxKind::RBracket, "]");
1166 b.finish_node();
1167 Constraint::cast(SyntaxNode::new_root(b.finish())).expect("Constraint casts")
1168 }
1169
1170 fn bound(builder: &mut GreenNodeBuilder<'static>, min: &str, max: Option<&str>) {
1172 builder.start_node(SyntaxKind::Bound.into());
1173 literal(builder, SyntaxKind::IntNumber, min);
1174 if let Some(max) = max {
1175 token(builder, SyntaxKind::DotDot, "..");
1176 literal(builder, SyntaxKind::IntNumber, max);
1177 }
1178 builder.finish_node();
1179 }
1180
1181 #[test]
1184 fn open_ranges_anchor_the_scalar_roles() {
1185 let upper_only = constraint(|b| {
1187 token(b, SyntaxKind::DotDot, "..");
1188 literal(b, SyntaxKind::IntNumber, "255");
1189 });
1190 assert!(upper_only.min().is_none());
1191 assert_eq!(upper_only.max().unwrap().syntax().text().to_string(), "255");
1192 assert!(upper_only.step().is_none());
1193 assert!(upper_only.match_pattern().is_none());
1194 assert!(upper_only.len().is_none());
1195
1196 let lower_only = constraint(|b| {
1198 literal(b, SyntaxKind::IntNumber, "0");
1199 token(b, SyntaxKind::DotDot, "..");
1200 });
1201 assert_eq!(lower_only.min().unwrap().syntax().text().to_string(), "0");
1202 assert!(lower_only.max().is_none());
1203 assert!(lower_only.step().is_none());
1204 assert!(lower_only.match_pattern().is_none());
1205 }
1206
1207 #[test]
1209 fn bare_match_pattern_is_not_a_scalar_endpoint() {
1210 let bare_match = constraint(|b| {
1211 token(b, SyntaxKind::MatchKw, "match");
1212 token(b, SyntaxKind::Whitespace, " ");
1213 literal(b, SyntaxKind::Ident, "PATTERN");
1214 });
1215 assert!(bare_match.min().is_none());
1216 assert!(bare_match.max().is_none());
1217 assert!(bare_match.step().is_none());
1218 assert!(bare_match.len().is_none());
1219 assert_eq!(
1220 bare_match
1221 .match_pattern()
1222 .unwrap()
1223 .syntax()
1224 .text()
1225 .to_string(),
1226 "PATTERN",
1227 );
1228 }
1229
1230 #[test]
1234 fn length_bound_literals_do_not_leak_into_the_scalar_accessors() {
1235 let exact_len = constraint(|b| {
1237 bound(b, "17", None);
1238 token(b, SyntaxKind::Whitespace, " ");
1239 token(b, SyntaxKind::MatchKw, "match");
1240 token(b, SyntaxKind::Whitespace, " ");
1241 literal(b, SyntaxKind::Regex, "/re/");
1242 });
1243 assert!(exact_len.min().is_none());
1244 assert!(exact_len.max().is_none());
1245 let len = exact_len.len().expect("the length bound is present");
1246 assert_eq!(len.min().unwrap().syntax().text().to_string(), "17");
1247 assert!(len.max().is_none());
1248 assert_eq!(
1249 exact_len
1250 .match_pattern()
1251 .unwrap()
1252 .syntax()
1253 .text()
1254 .to_string(),
1255 "/re/",
1256 );
1257
1258 let len_range = constraint(|b| {
1260 bound(b, "3", Some("6"));
1261 token(b, SyntaxKind::Whitespace, " ");
1262 token(b, SyntaxKind::MatchKw, "match");
1263 token(b, SyntaxKind::Whitespace, " ");
1264 literal(b, SyntaxKind::Ident, "P");
1265 });
1266 assert!(len_range.min().is_none());
1267 assert!(len_range.max().is_none());
1268 let len = len_range.len().expect("the length bound is present");
1269 assert_eq!(len.min().unwrap().syntax().text().to_string(), "3");
1270 assert_eq!(len.max().unwrap().syntax().text().to_string(), "6");
1271 assert_eq!(
1272 len_range
1273 .match_pattern()
1274 .unwrap()
1275 .syntax()
1276 .text()
1277 .to_string(),
1278 "P",
1279 );
1280 }
1281
1282 #[test]
1285 fn attribute_and_expr_accessors_read_a_parsed_contract() {
1286 use crate::{Profile, parse};
1287
1288 let input = "package p\ninterface I {\n \
1289 command setGear(position: GearPosition) [\n \
1290 require position != GearPosition.PARK || currentSpeed == 0.0\n \
1291 someKey = 3\n ]\n}\n";
1292 let parsed = parse(input, Profile::Ridl);
1293 assert!(parsed.errors().is_empty(), "got: {:?}", parsed.errors());
1294
1295 let attr_block = parsed
1296 .syntax()
1297 .descendants()
1298 .find_map(AttrBlock::cast)
1299 .expect("an AttrBlock parses");
1300 let attributes: Vec<Attribute> = attr_block.attributes().collect();
1301 assert_eq!(attributes.len(), 2);
1302
1303 let predicate = &attributes[0];
1305 assert_eq!(predicate.predicate_kind(), Some(PredicateKind::Require));
1306 assert!(predicate.key().is_none());
1307 assert!(predicate.value().is_none());
1308 let Some(Expr::Binary(or)) = predicate.expr() else {
1309 panic!("expected a binary root, got {:?}", predicate.expr());
1310 };
1311 assert_eq!(or.op_token().unwrap().text(), "||");
1312 let Some(Expr::Binary(neq)) = or.lhs() else {
1313 panic!("expected a binary lhs, got {:?}", or.lhs());
1314 };
1315 assert_eq!(neq.op_token().unwrap().text(), "!=");
1316 let Some(Expr::Member(member)) = neq.rhs() else {
1317 panic!("expected a member rhs, got {:?}", neq.rhs());
1318 };
1319 assert_eq!(member.member_token().unwrap().text(), "PARK");
1320 let Some(Expr::Path(base)) = member.base() else {
1321 panic!("expected a path base, got {:?}", member.base());
1322 };
1323 assert_eq!(base.name_token().unwrap().text(), "GearPosition");
1324 let Some(Expr::Binary(eq)) = or.rhs() else {
1325 panic!("expected a binary rhs, got {:?}", or.rhs());
1326 };
1327 let Some(Expr::Literal(zero)) = eq.rhs() else {
1328 panic!("expected a literal rhs, got {:?}", eq.rhs());
1329 };
1330 assert_eq!(zero.token().unwrap().text(), "0.0");
1331
1332 let assignment = &attributes[1];
1334 assert!(assignment.predicate_kind().is_none());
1335 assert!(assignment.expr().is_none());
1336 assert_eq!(
1337 assignment.key().unwrap().ident_token().unwrap().text(),
1338 "someKey",
1339 );
1340 let value = assignment.value().expect("the assignment has a value");
1341 assert_eq!(value.literal().unwrap().syntax().text().to_string(), "3");
1342 }
1343
1344 const THREE_SHAPES: &str = "\
1347package app
1348
1349interface VehicleStatus {
1350 signal speed : Speed @10ms
1351 event doorOpened : DoorState
1352}
1353
1354service veh.adas.status : VehicleStatus
1355
1356service veh.adas.logs {
1357 signal level : Level @1s
1358 reserved oldTail
1359 query tailLogs(filter: Filter): Line
1360}
1361";
1362
1363 #[test]
1368 fn shapes_walks_interfaces_and_inline_service_bodies() {
1369 use crate::{Profile, parse};
1370
1371 let parsed = parse(THREE_SHAPES, Profile::Ridl);
1372 assert!(parsed.errors().is_empty(), "got: {:?}", parsed.errors());
1373 let file = SourceFile::cast(parsed.syntax()).expect("the root is a SourceFile");
1374
1375 let walk: Vec<(String, usize)> = file
1376 .shapes()
1377 .map(|shape| {
1378 (
1379 shape.identity().expect("every shape here is named"),
1380 shape.members().count(),
1381 )
1382 })
1383 .collect();
1384 assert_eq!(
1385 walk,
1386 [
1387 ("VehicleStatus".to_string(), 2),
1388 ("veh.adas.logs".to_string(), 3),
1389 ],
1390 "the interface, then the inline shape under the service's dotted \
1391 name; the reference-form service contributes nothing",
1392 );
1393
1394 assert_eq!(file.services().count(), 2);
1396 assert!(
1397 !file
1398 .shapes()
1399 .any(|shape| shape.identity().as_deref() == Some("veh.adas.status")),
1400 );
1401 }
1402
1403 #[test]
1406 fn shape_identity_range_covers_the_declared_name() {
1407 use crate::{Profile, parse};
1408
1409 let parsed = parse(THREE_SHAPES, Profile::Ridl);
1410 let file = SourceFile::cast(parsed.syntax()).expect("the root is a SourceFile");
1411
1412 for shape in file.shapes() {
1413 let name = shape.identity().expect("named");
1414 let range = shape.identity_range().expect("named");
1415 let start = usize::from(range.start());
1416 let end = usize::from(range.end());
1417 assert_eq!(
1418 &THREE_SHAPES[start..end],
1419 name,
1420 "the range must cover exactly the declared name",
1421 );
1422 }
1423 }
1424
1425 #[test]
1430 fn a_service_with_neither_form_is_an_empty_inline_shape() {
1431 use crate::{Profile, parse};
1432
1433 let parsed = parse("package app\nservice veh.adas.broken\n", Profile::Ridl);
1434 let file = SourceFile::cast(parsed.syntax()).expect("the root is a SourceFile");
1435
1436 let shapes: Vec<InterfaceShape> = file.shapes().collect();
1437 assert_eq!(shapes.len(), 1);
1438 assert_eq!(shapes[0].identity().as_deref(), Some("veh.adas.broken"));
1439 assert_eq!(shapes[0].members().count(), 0);
1440 }
1441
1442 #[test]
1445 fn dotted_name_text_joins_the_segments() {
1446 use crate::{Profile, parse};
1447
1448 let parsed = parse(
1449 "package app\nservice veh . adas . logs {\n}\n",
1450 Profile::Ridl,
1451 );
1452 let file = SourceFile::cast(parsed.syntax()).expect("the root is a SourceFile");
1453 let service = file.services().next().expect("a service parses");
1454 assert_eq!(service.name().expect("named").text(), "veh.adas.logs");
1455 }
1456
1457 #[test]
1458 fn enum_set_def_derived_form_exposes_the_backing_ref() {
1459 let mut b = GreenNodeBuilder::new();
1460 b.start_node(SyntaxKind::SourceFile.into());
1461 b.start_node(SyntaxKind::EnumSetDef.into());
1462 token(&mut b, SyntaxKind::EnumsetKw, "enumset");
1463 token(&mut b, SyntaxKind::Whitespace, " ");
1464 name(&mut b, "WarningFlags");
1465 token(&mut b, SyntaxKind::Colon, ":");
1466 token(&mut b, SyntaxKind::Whitespace, " ");
1467 b.start_node(SyntaxKind::PathType.into());
1468 b.start_node(SyntaxKind::QualifiedName.into());
1469 token(&mut b, SyntaxKind::Ident, "Warning");
1470 b.finish_node();
1471 b.finish_node();
1472 b.finish_node();
1473 b.finish_node();
1474 let root = SyntaxNode::new_root(b.finish());
1475
1476 let file = SourceFile::cast(root).expect("root is a SourceFile");
1477 let Some(Definition::EnumSet(enum_set)) = file.definitions().next() else {
1478 panic!("expected an enumset definition");
1479 };
1480 assert_eq!(
1481 enum_set.name().unwrap().ident_token().unwrap().text(),
1482 "WarningFlags",
1483 );
1484 assert_eq!(
1485 enum_set.backing_ref().unwrap().syntax().text().to_string(),
1486 "Warning",
1487 );
1488 assert_eq!(enum_set.bits().count(), 0);
1489 }
1490}