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