relm4_macros/widgets/parse/
properties.rs1use proc_macro2::{Literal, Punct};
2use syn::ext::IdentExt;
3use syn::parse::ParseStream;
4use syn::parse::discouraged::Speculative;
5use syn::punctuated::{Pair, Punctuated};
6use syn::token::{And, At, Caret, Colon, Dot, Gt, Lt, Or, Question, Slash, Tilde, Underscore};
7use syn::{Ident, Lifetime, Token, braced, bracketed, parenthesized, token};
8
9use crate::widgets::{ParseError, Properties, Property, PropertyName, PropertyType, parse_util};
10
11impl Properties {
12 pub(super) fn parse(input: ParseStream<'_>) -> Self {
13 let mut props: Punctuated<Property, Token![,]> = Punctuated::new();
14 loop {
15 if input.is_empty() {
16 break;
17 }
18 let parse_input = input.fork();
19 let (prop, contains_error) = Property::parse(&parse_input);
20 props.push(prop);
21
22 if !contains_error {
24 input.advance_to(&parse_input);
25 }
26
27 if input.is_empty() {
28 break;
29 }
30
31 if let Some(prop) = parse_comma_error(input) {
32 if contains_error {
34 while !parse_next_token(input).unwrap() {
36 let next_input = input.fork();
37 let (prop, contains_error) = Property::parse(&next_input);
38 if !contains_error {
39 props.push(prop);
41 input.advance_to(&next_input);
42
43 if let Some(prop) = parse_comma_error(input) {
45 props.push(prop);
46 }
47 break;
48 }
49 }
50 } else {
51 props.push(prop);
52 }
53 }
54 }
55
56 let properties = props.into_pairs().map(Pair::into_value).collect();
57 Properties { properties }
58 }
59}
60
61fn parse_comma_error(input: ParseStream<'_>) -> Option<Property> {
62 let lookahead = input.lookahead1();
63 if lookahead.peek(Token![,]) {
64 input.parse::<Token![,]>().unwrap();
65 None
66 } else {
67 let err = lookahead.error();
68 Some(Property {
69 name: PropertyName::Ident(parse_util::string_to_snake_case("comma_error")),
70 ty: PropertyType::ParseError(ParseError::Generic(err.to_compile_error())),
71 })
72 }
73}
74
75macro_rules! parse_type {
76 ($input:ident, $ty:ty) => {
77 let _: $ty = $input.parse()?;
78 return Ok(false);
79 };
80}
81
82fn skip_inner_tokens(input: ParseStream<'_>) -> Result<(), syn::Error> {
83 while !input.is_empty() {
84 parse_next_token(input)?;
85 }
86 Ok(())
87}
88
89fn parse_next_token(input: ParseStream<'_>) -> Result<bool, syn::Error> {
90 let inner_tokens;
91 if input.is_empty() {
92 Ok(true)
93 } else if input.peek(Token![,]) {
94 let _comma: Token![,] = input.parse()?;
95 Ok(true)
96 } else if input.peek(token::Paren) {
97 parenthesized!(inner_tokens in input);
98 skip_inner_tokens(&inner_tokens)?;
99 Ok(false)
100 } else if input.peek(token::Bracket) {
101 bracketed!(inner_tokens in input);
102 skip_inner_tokens(&inner_tokens)?;
103 Ok(false)
104 } else if input.peek(token::Brace) {
105 braced!(inner_tokens in input);
106 skip_inner_tokens(&inner_tokens)?;
107 Ok(false)
108 } else if Ident::parse_any(input).is_ok() {
109 Ok(false)
110 } else if input.peek(And) {
111 parse_type!(input, And);
112 } else if input.peek(At) {
113 parse_type!(input, At);
114 } else if input.peek(Colon) {
115 parse_type!(input, Colon);
116 } else if input.peek(Slash) {
117 parse_type!(input, Slash);
118 } else if input.peek(token::Eq) {
119 parse_type!(input, token::Eq);
120 } else if input.peek(Gt) {
121 parse_type!(input, Gt);
122 } else if input.peek(Lt) {
123 parse_type!(input, Lt);
124 } else if input.peek(Or) {
125 parse_type!(input, Or);
126 } else if input.peek(Tilde) {
127 parse_type!(input, Tilde);
128 } else if input.peek(Caret) {
129 parse_type!(input, Caret);
130 } else if input.peek(Underscore) {
131 parse_type!(input, Underscore);
132 } else if input.peek(Question) {
133 parse_type!(input, Question);
134 } else if input.peek(Dot) {
135 parse_type!(input, Dot);
136 } else if input.peek(Lifetime) {
137 parse_type!(input, Lifetime);
138 } else if input.parse::<Punct>().is_ok() || input.parse::<Literal>().is_ok() {
139 Ok(false)
140 } else {
141 unreachable!(
142 "Every possible token should be covered. Please report this error at Relm4! \nContext: '''{input}''' \n"
143 );
144 }
145}