1use proc_macro::TokenStream;
6use proc_macro2::TokenStream as TokenStream2;
7use quote::quote;
8use syn::parse::{Parse, ParseStream};
9use syn::{Expr, Ident, LitBool, LitInt, Result, Token, bracketed, parenthesized};
10
11#[proc_macro]
23pub fn step(input: TokenStream) -> TokenStream {
24 let parsed = syn::parse_macro_input!(input as StepMacroInput);
25
26 let signal = parsed.signal;
27 let value = parsed.value;
28
29 let ts_tokens = match parsed.timestamp {
30 StepTimestamp::Suffixed(lit) => match duration_expr_from_lit(&lit) {
31 Ok(tokens) => tokens,
32 Err(err) => return err.to_compile_error().into(),
33 },
34 StepTimestamp::Expr(expr) => quote! { #expr },
35 };
36
37 quote! {
38 ::mstlo::Step::new(#signal, #value, #ts_tokens)
39 }
40 .into()
41}
42
43struct StepMacroInput {
44 signal: Expr,
45 value: Expr,
46 timestamp: StepTimestamp,
47}
48
49enum StepTimestamp {
50 Suffixed(LitInt),
51 Expr(Expr),
52}
53
54impl Parse for StepMacroInput {
55 fn parse(input: ParseStream) -> Result<Self> {
56 let signal = input.parse::<Expr>()?;
57 input.parse::<Token![,]>()?;
58 let value = input.parse::<Expr>()?;
59 input.parse::<Token![,]>()?;
60
61 let timestamp = if input.peek(LitInt) {
62 StepTimestamp::Suffixed(input.parse::<LitInt>()?)
63 } else {
64 StepTimestamp::Expr(input.parse::<Expr>()?)
65 };
66
67 if !input.is_empty() {
68 return Err(input.error("unexpected tokens after timestamp argument"));
69 }
70
71 Ok(Self {
72 signal,
73 value,
74 timestamp,
75 })
76 }
77}
78
79fn duration_expr_from_lit(lit: &LitInt) -> Result<TokenStream2> {
80 let value = lit.base10_parse::<u64>()?;
81 let suffix = lit.suffix();
82
83 match suffix {
84 "ns" => Ok(quote! { ::std::time::Duration::from_nanos(#value) }),
85 "us" => Ok(quote! { ::std::time::Duration::from_micros(#value) }),
86 "ms" => Ok(quote! { ::std::time::Duration::from_millis(#value) }),
87 "s" => Ok(quote! { ::std::time::Duration::from_secs(#value) }),
88 _ => Err(syn::Error::new(
89 lit.span(),
90 "unsupported duration suffix; use one of: ns, us, ms, s",
91 )),
92 }
93}
94
95#[proc_macro]
150pub fn stl(input: TokenStream) -> TokenStream {
151 let input2 = TokenStream2::from(input);
152
153 match parse_stl_formula(input2) {
154 Ok(output) => output.into(),
155 Err(err) => err.to_compile_error().into(),
156 }
157}
158
159fn parse_stl_formula(input: TokenStream2) -> Result<TokenStream2> {
160 let formula: StlFormula = syn::parse2(input)?;
162 Ok(formula.to_token_stream())
163}
164
165enum PredicateValue {
167 Const(Expr),
169 Var(Ident),
171}
172
173enum StlFormula {
175 True,
176 False,
177 GreaterThan(Ident, Expr),
178 LessThan(Ident, Expr),
179 GreaterThanVar(Ident, Ident),
181 LessThanVar(Ident, Ident),
183 Not(Box<StlFormula>),
184 And(Box<StlFormula>, Box<StlFormula>),
185 Or(Box<StlFormula>, Box<StlFormula>),
186 Implies(Box<StlFormula>, Box<StlFormula>),
187 Globally(Expr, Expr, Box<StlFormula>),
188 Eventually(Expr, Expr, Box<StlFormula>),
189 Until(Expr, Expr, Box<StlFormula>, Box<StlFormula>),
190 Interpolated(Expr),
192}
193
194impl StlFormula {
195 fn to_token_stream(&self) -> TokenStream2 {
196 match self {
197 StlFormula::True => quote! {
198 ::mstlo::FormulaDefinition::True
199 },
200 StlFormula::False => quote! {
201 ::mstlo::FormulaDefinition::False
202 },
203 StlFormula::GreaterThan(signal, val) => {
204 let signal_str = signal.to_string();
205 quote! {
206 ::mstlo::FormulaDefinition::GreaterThan(#signal_str, #val as f64)
207 }
208 }
209 StlFormula::LessThan(signal, val) => {
210 let signal_str = signal.to_string();
211 quote! {
212 ::mstlo::FormulaDefinition::LessThan(#signal_str, #val as f64)
213 }
214 }
215 StlFormula::GreaterThanVar(signal, var) => {
216 let signal_str = signal.to_string();
217 let var_str = var.to_string();
218 quote! {
219 ::mstlo::FormulaDefinition::GreaterThanVar(#signal_str, #var_str)
220 }
221 }
222 StlFormula::LessThanVar(signal, var) => {
223 let signal_str = signal.to_string();
224 let var_str = var.to_string();
225 quote! {
226 ::mstlo::FormulaDefinition::LessThanVar(#signal_str, #var_str)
227 }
228 }
229 StlFormula::Not(sub) => {
230 let sub_tokens = sub.to_token_stream();
231 quote! {
232 ::mstlo::FormulaDefinition::Not(
233 Box::new(#sub_tokens)
234 )
235 }
236 }
237 StlFormula::And(left, right) => {
238 let left_tokens = left.to_token_stream();
239 let right_tokens = right.to_token_stream();
240 quote! {
241 ::mstlo::FormulaDefinition::And(
242 Box::new(#left_tokens),
243 Box::new(#right_tokens)
244 )
245 }
246 }
247 StlFormula::Or(left, right) => {
248 let left_tokens = left.to_token_stream();
249 let right_tokens = right.to_token_stream();
250 quote! {
251 ::mstlo::FormulaDefinition::Or(
252 Box::new(#left_tokens),
253 Box::new(#right_tokens)
254 )
255 }
256 }
257 StlFormula::Implies(left, right) => {
258 let left_tokens = left.to_token_stream();
259 let right_tokens = right.to_token_stream();
260 quote! {
261 ::mstlo::FormulaDefinition::Implies(
262 Box::new(#left_tokens),
263 Box::new(#right_tokens)
264 )
265 }
266 }
267 StlFormula::Globally(start, end, sub) => {
268 let sub_tokens = sub.to_token_stream();
269 quote! {
270 ::mstlo::FormulaDefinition::Globally(
271 ::mstlo::TimeInterval {
272 start: ::std::time::Duration::from_secs(#start as u64),
273 end: ::std::time::Duration::from_secs(#end as u64),
274 },
275 Box::new(#sub_tokens)
276 )
277 }
278 }
279 StlFormula::Eventually(start, end, sub) => {
280 let sub_tokens = sub.to_token_stream();
281 quote! {
282 ::mstlo::FormulaDefinition::Eventually(
283 ::mstlo::TimeInterval {
284 start: ::std::time::Duration::from_secs(#start as u64),
285 end: ::std::time::Duration::from_secs(#end as u64),
286 },
287 Box::new(#sub_tokens)
288 )
289 }
290 }
291 StlFormula::Until(start, end, left, right) => {
292 let left_tokens = left.to_token_stream();
293 let right_tokens = right.to_token_stream();
294 quote! {
295 ::mstlo::FormulaDefinition::Until(
296 ::mstlo::TimeInterval {
297 start: ::std::time::Duration::from_secs(#start as u64),
298 end: ::std::time::Duration::from_secs(#end as u64),
299 },
300 Box::new(#left_tokens),
301 Box::new(#right_tokens)
302 )
303 }
304 }
305 StlFormula::Interpolated(expr) => {
306 quote! { #expr }
307 }
308 }
309 }
310}
311
312impl Parse for StlFormula {
313 fn parse(input: ParseStream) -> Result<Self> {
314 parse_expr(input, 0) }
316}
317
318fn get_binary_op_precedence(input: ParseStream) -> Option<u8> {
325 if input.peek(Token![->]) {
326 return Some(1);
327 }
328 if input.peek(Token![||]) {
329 return Some(2);
330 }
331 if input.peek(Token![&&]) {
332 return Some(3);
333 }
334 if input.peek(Ident) {
335 let fork = input.fork();
336 if let Ok(ident) = fork.parse::<Ident>() {
337 match ident.to_string().as_str() {
338 "implies" => return Some(1),
339 "or" => return Some(2),
340 "and" => return Some(3),
341 "U" | "until" => return Some(4),
342 _ => {}
343 }
344 }
345 }
346 None
347}
348
349fn parse_expr(input: ParseStream, min_prec: u8) -> Result<StlFormula> {
351 let mut left = parse_primary(input)?;
352
353 while let Some(prec) = get_binary_op_precedence(input) {
354 if prec < min_prec {
355 break;
356 }
357
358 left = parse_binary_op(input, left, prec)?;
359 }
360
361 Ok(left)
362}
363
364fn parse_binary_op(input: ParseStream, left: StlFormula, prec: u8) -> Result<StlFormula> {
366 if input.peek(Token![&&]) {
368 let op_token = input.parse::<Token![&&]>()?;
369 if input.is_empty() {
370 return Err(syn::Error::new(
371 op_token.spans[0],
372 "missing right operand after '&&'\n help: conjunction requires a formula on both sides, e.g., `x > 0 && y < 10`",
373 ));
374 }
375 let right = parse_expr(input, prec + 1)?;
376 return Ok(StlFormula::And(Box::new(left), Box::new(right)));
377 }
378
379 if input.peek(Token![||]) {
380 let op_token = input.parse::<Token![||]>()?;
381 if input.is_empty() {
382 return Err(syn::Error::new(
383 op_token.spans[0],
384 "missing right operand after '||'\n help: disjunction requires a formula on both sides, e.g., `x > 0 || y < 10`",
385 ));
386 }
387 let right = parse_expr(input, prec + 1)?;
388 return Ok(StlFormula::Or(Box::new(left), Box::new(right)));
389 }
390
391 if input.peek(Token![->]) {
392 let op_token = input.parse::<Token![->]>()?;
393 if input.is_empty() {
394 return Err(syn::Error::new(
395 op_token.spans[0],
396 "missing right operand after '->'\n help: implication requires a formula on both sides, e.g., `x > 0 -> y < 10`",
397 ));
398 }
399 let right = parse_expr(input, prec)?;
401 return Ok(StlFormula::Implies(Box::new(left), Box::new(right)));
402 }
403
404 if input.peek(Ident) {
405 let fork = input.fork();
406 let ident: Ident = fork.parse()?;
407 let ident_str = ident.to_string();
408
409 match ident_str.as_str() {
410 "and" => {
411 let op_ident = input.parse::<Ident>()?;
412 if input.is_empty() {
413 return Err(syn::Error::new(
414 op_ident.span(),
415 "missing right operand after 'and'\n help: conjunction requires a formula on both sides, e.g., `(x > 0) and (y < 10)`",
416 ));
417 }
418 let right = parse_expr(input, prec + 1)?;
419 return Ok(StlFormula::And(Box::new(left), Box::new(right)));
420 }
421 "or" => {
422 let op_ident = input.parse::<Ident>()?;
423 if input.is_empty() {
424 return Err(syn::Error::new(
425 op_ident.span(),
426 "missing right operand after 'or'\n help: disjunction requires a formula on both sides, e.g., `(x > 0) or (y < 10)`",
427 ));
428 }
429 let right = parse_expr(input, prec + 1)?;
430 return Ok(StlFormula::Or(Box::new(left), Box::new(right)));
431 }
432 "implies" => {
433 let op_ident = input.parse::<Ident>()?;
434 if input.is_empty() {
435 return Err(syn::Error::new(
436 op_ident.span(),
437 "missing right operand after 'implies'\n help: implication requires a formula on both sides, e.g., `(x > 0) implies (y < 10)`",
438 ));
439 }
440 let right = parse_expr(input, prec)?;
442 return Ok(StlFormula::Implies(Box::new(left), Box::new(right)));
443 }
444 "U" | "until" => {
445 let op_ident = input.parse::<Ident>()?;
446
447 if !input.peek(syn::token::Bracket) {
448 return Err(syn::Error::new(
449 op_ident.span(),
450 format!(
451 "missing time interval after '{}'\n \
452 help: the '{}' operator requires a time interval, e.g., `left {}[0, 10] right`",
453 ident_str, ident_str, ident_str
454 ),
455 ));
456 }
457
458 let (start, end) = parse_time_interval(input)?;
459
460 if input.is_empty() {
461 return Err(syn::Error::new(
462 op_ident.span(),
463 format!(
464 "missing right operand after '{}[_, _]'\n \
465 help: until requires a formula on both sides, e.g., `(x > 0) {}[0, 10] (y < 10)`",
466 ident_str, ident_str
467 ),
468 ));
469 }
470
471 let right = parse_expr(input, prec + 1)?;
472 return Ok(StlFormula::Until(
473 start,
474 end,
475 Box::new(left),
476 Box::new(right),
477 ));
478 }
479 _ => {}
480 }
481 }
482
483 Err(syn::Error::new(
484 input.span(),
485 "expected binary operator (&&, ||, ->, and, or, implies, U[_, _], until[_, _])",
486 ))
487}
488
489fn parse_primary(input: ParseStream) -> Result<StlFormula> {
491 if input.peek(LitBool) {
493 let lit: LitBool = input.parse()?;
494 return if lit.value {
495 Ok(StlFormula::True)
496 } else {
497 Ok(StlFormula::False)
498 };
499 }
500
501 if input.peek(Token![!]) {
503 return parse_not(input);
504 }
505
506 if input.peek(Ident) {
508 let fork = input.fork();
509 let ident: Ident = fork.parse()?;
510 let ident_str = ident.to_string();
511
512 match ident_str.as_str() {
513 "not" => return parse_not_keyword(input),
514 "G" | "globally" => return parse_globally(input),
515 "F" | "eventually" => return parse_eventually(input),
516 _ => {
517 if let Ok(predicate) = try_parse_predicate(input) {
519 return Ok(predicate);
520 }
521 }
522 }
523 }
524
525 if input.peek(syn::token::Paren) {
527 let content;
528 parenthesized!(content in input);
529 let inner = parse_expr(&content, 0)?;
530
531 if !content.is_empty() {
532 return Err(content.error("unexpected tokens inside parentheses"));
533 }
534
535 return Ok(inner);
536 }
537
538 let expr: Expr = input.parse()?;
540 Ok(StlFormula::Interpolated(expr))
541}
542
543fn parse_not(input: ParseStream) -> Result<StlFormula> {
545 let not_token = input.parse::<Token![!]>()?;
546
547 if input.is_empty() {
548 return Err(syn::Error::new(
549 not_token.span,
550 "missing operand after '!'\n \
551 help: negation requires a subformula, e.g., `!(x > 5)` or `!true`",
552 ));
553 }
554
555 if input.peek(syn::token::Paren) {
557 let content;
558 parenthesized!(content in input);
559
560 if content.is_empty() {
561 return Err(content.error(
562 "empty subformula in negation\n \
563 help: negation requires a subformula, e.g., `!(x > 5)`",
564 ));
565 }
566
567 let sub = parse_expr(&content, 0)?;
568
569 if !content.is_empty() {
570 return Err(content.error(
571 "unexpected tokens after negation subformula\n \
572 help: if you want to negate a complex formula, wrap it in parentheses: `!(a && b)`",
573 ));
574 }
575
576 Ok(StlFormula::Not(Box::new(sub)))
577 } else {
578 let sub = parse_primary(input)?;
580 Ok(StlFormula::Not(Box::new(sub)))
581 }
582}
583
584fn parse_not_keyword(input: ParseStream) -> Result<StlFormula> {
586 let ident: Ident = input.parse()?;
587 if ident != "not" {
588 return Err(syn::Error::new(ident.span(), "expected 'not'"));
589 }
590
591 if input.is_empty() {
592 return Err(syn::Error::new(
593 ident.span(),
594 "missing operand after 'not'\n \
595 help: negation requires a subformula, e.g., `not(x > 5)` or `not true`",
596 ));
597 }
598
599 if input.peek(syn::token::Paren) {
601 let content;
602 parenthesized!(content in input);
603
604 if content.is_empty() {
605 return Err(content.error(
606 "empty subformula in negation\n \
607 help: negation requires a subformula, e.g., `not(x > 5)`",
608 ));
609 }
610
611 let sub = parse_expr(&content, 0)?;
612
613 if !content.is_empty() {
614 return Err(content.error(
615 "unexpected tokens after negation subformula\n \
616 help: if you want to negate a complex formula, wrap it in parentheses: `not(a and b)`"
617 ));
618 }
619
620 Ok(StlFormula::Not(Box::new(sub)))
621 } else {
622 let sub = parse_primary(input)?;
623 Ok(StlFormula::Not(Box::new(sub)))
624 }
625}
626
627fn parse_time_interval(input: ParseStream) -> Result<(Expr, Expr)> {
629 let content;
630 bracketed!(content in input);
631
632 if content.is_empty() {
633 return Err(content.error(
634 "empty time interval\n \
635 help: time intervals require two values [start, end], e.g., [0, 10]",
636 ));
637 }
638
639 let start: Expr = content.parse().map_err(|_| {
640 syn::Error::new(
641 content.span(),
642 "invalid start value in time interval\n \
643 help: expected a numeric expression for the interval start, e.g., [0, 10]",
644 )
645 })?;
646
647 if !content.peek(Token![,]) {
648 return Err(syn::Error::new(
649 content.span(),
650 "missing comma in time interval\n \
651 help: time intervals must have two comma-separated values [start, end], e.g., [0, 10]",
652 ));
653 }
654 content.parse::<Token![,]>()?;
655
656 if content.is_empty() {
657 return Err(content.error(
658 "missing end value in time interval\n \
659 help: time intervals require two values [start, end], e.g., [0, 10]",
660 ));
661 }
662
663 let end: Expr = content.parse().map_err(|_| {
664 syn::Error::new(
665 content.span(),
666 "invalid end value in time interval\n \
667 help: expected a numeric expression for the interval end, e.g., [0, 10]",
668 )
669 })?;
670
671 if !content.is_empty() {
672 return Err(content.error(
673 "too many values in time interval\n \
674 help: time intervals take exactly two values [start, end], e.g., [0, 10]",
675 ));
676 }
677
678 Ok((start, end))
679}
680
681fn parse_globally(input: ParseStream) -> Result<StlFormula> {
683 let ident: Ident = input.parse()?;
684 let ident_str = ident.to_string();
685
686 if ident_str != "G" && ident_str != "globally" {
687 return Err(syn::Error::new(
688 ident.span(),
689 format!("expected 'G' or 'globally', found '{}'", ident_str),
690 ));
691 }
692
693 if !input.peek(syn::token::Bracket) {
694 return Err(syn::Error::new(
695 ident.span(),
696 format!(
697 "missing time interval after '{}'\n \
698 help: the globally operator requires a time interval, e.g., `{}[0, 10](x > 5)`\n \
699 note: '{}' means \"always\" within the given time window",
700 ident_str, ident_str, ident_str
701 ),
702 ));
703 }
704
705 let (start, end) = parse_time_interval(input)?;
706
707 let sub = if input.peek(syn::token::Paren) {
709 let content;
710 parenthesized!(content in input);
711
712 if content.is_empty() {
713 return Err(content.error(
714 "empty subformula in globally operator\n \
715 help: globally requires a subformula, e.g., `G[0, 10](x > 5)`",
716 ));
717 }
718
719 let sub = parse_expr(&content, 0)?;
720
721 if !content.is_empty() {
722 return Err(content.error(
723 "unexpected tokens after globally subformula\n \
724 help: if you want to combine formulas, use binary operators outside the parentheses"
725 ));
726 }
727 sub
728 } else if input.is_empty() {
729 return Err(syn::Error::new(
730 ident.span(),
731 format!(
732 "missing subformula after '{}[_, _]'\n \
733 help: globally requires a subformula, e.g., `{}[0, 10](x > 5)` or `{}[0, 10] x > 5`",
734 ident_str, ident_str, ident_str
735 ),
736 ));
737 } else {
738 parse_primary(input)?
739 };
740
741 Ok(StlFormula::Globally(start, end, Box::new(sub)))
742}
743
744fn parse_eventually(input: ParseStream) -> Result<StlFormula> {
746 let ident: Ident = input.parse()?;
747 let ident_str = ident.to_string();
748
749 if ident_str != "F" && ident_str != "eventually" {
750 return Err(syn::Error::new(
751 ident.span(),
752 format!("expected 'F' or 'eventually', found '{}'", ident_str),
753 ));
754 }
755
756 if !input.peek(syn::token::Bracket) {
757 return Err(syn::Error::new(
758 ident.span(),
759 format!(
760 "missing time interval after '{}'\n \
761 help: the eventually operator requires a time interval, e.g., `{}[0, 10](x > 5)`\n \
762 note: '{}' means \"at some point\" within the given time window",
763 ident_str, ident_str, ident_str
764 ),
765 ));
766 }
767
768 let (start, end) = parse_time_interval(input)?;
769
770 let sub = if input.peek(syn::token::Paren) {
772 let content;
773 parenthesized!(content in input);
774
775 if content.is_empty() {
776 return Err(content.error(
777 "empty subformula in eventually operator\n \
778 help: eventually requires a subformula, e.g., `F[0, 10](x > 5)`",
779 ));
780 }
781
782 let sub = parse_expr(&content, 0)?;
783
784 if !content.is_empty() {
785 return Err(content.error(
786 "unexpected tokens after eventually subformula\n \
787 help: if you want to combine formulas, use binary operators outside the parentheses"
788 ));
789 }
790 sub
791 } else if input.is_empty() {
792 return Err(syn::Error::new(
793 ident.span(),
794 format!(
795 "missing subformula after '{}[_, _]'\n \
796 help: eventually requires a subformula, e.g., `{}[0, 10](x > 5)` or `{}[0, 10] x > 5`",
797 ident_str, ident_str, ident_str
798 ),
799 ));
800 } else {
801 parse_primary(input)?
802 };
803
804 Ok(StlFormula::Eventually(start, end, Box::new(sub)))
805}
806
807fn try_parse_predicate(input: ParseStream) -> Result<StlFormula> {
809 let fork = input.fork();
810
811 if !fork.peek(Ident) {
813 return Err(syn::Error::new(input.span(), "expected signal identifier"));
814 }
815
816 let signal: Ident = fork.parse()?;
817 let signal_str = signal.to_string();
818
819 if matches!(
821 signal_str.as_str(),
822 "true"
823 | "false"
824 | "not"
825 | "and"
826 | "or"
827 | "implies"
828 | "G"
829 | "F"
830 | "U"
831 | "globally"
832 | "eventually"
833 | "until"
834 ) {
835 return Err(syn::Error::new(
836 signal.span(),
837 format!(
838 "'{}' is a reserved keyword and cannot be used as a signal name\n \
839 help: use a different identifier for your signal",
840 signal_str
841 ),
842 ));
843 }
844
845 if fork.peek(Token![>]) && !fork.peek(Token![>=]) {
847 fork.parse::<Token![>]>()?;
848 let val = parse_predicate_value(&fork).map_err(|_| {
849 syn::Error::new(
850 fork.span(),
851 format!(
852 "invalid value after '>' in predicate\n \
853 help: expected a numeric value, e.g., `{} > 5` or `{} > $threshold`",
854 signal_str, signal_str
855 ),
856 )
857 })?;
858
859 input.parse::<Ident>()?;
861 input.parse::<Token![>]>()?;
862 let _: PredicateValue = parse_predicate_value(input)?;
863
864 return match val {
865 PredicateValue::Const(expr) => Ok(StlFormula::GreaterThan(signal, expr)),
866 PredicateValue::Var(var_ident) => Ok(StlFormula::GreaterThanVar(signal, var_ident)),
867 };
868 }
869
870 if fork.peek(Token![<]) && !fork.peek(Token![<=]) {
871 fork.parse::<Token![<]>()?;
872 let val = parse_predicate_value(&fork).map_err(|_| {
873 syn::Error::new(
874 fork.span(),
875 format!(
876 "invalid value after '<' in predicate\n \
877 help: expected a numeric value, e.g., `{} < 5` or `{} < $threshold`",
878 signal_str, signal_str
879 ),
880 )
881 })?;
882
883 input.parse::<Ident>()?;
885 input.parse::<Token![<]>()?;
886 let _: PredicateValue = parse_predicate_value(input)?;
887
888 return match val {
889 PredicateValue::Const(expr) => Ok(StlFormula::LessThan(signal, expr)),
890 PredicateValue::Var(var_ident) => Ok(StlFormula::LessThanVar(signal, var_ident)),
891 };
892 }
893
894 if fork.peek(Token![>=]) {
895 fork.parse::<Token![>=]>()?;
896 let val = parse_predicate_value(&fork).map_err(|_| {
897 syn::Error::new(
898 fork.span(),
899 format!(
900 "invalid value after '>=' in predicate\n \
901 help: expected a numeric value, e.g., `{} >= 5` or `{} >= $threshold`",
902 signal_str, signal_str
903 ),
904 )
905 })?;
906
907 input.parse::<Ident>()?;
909 input.parse::<Token![>=]>()?;
910 let _: PredicateValue = parse_predicate_value(input)?;
911
912 return match val {
914 PredicateValue::Const(expr) => Ok(StlFormula::Not(Box::new(StlFormula::LessThan(
915 signal, expr,
916 )))),
917 PredicateValue::Var(var_ident) => Ok(StlFormula::Not(Box::new(
918 StlFormula::LessThanVar(signal, var_ident),
919 ))),
920 };
921 }
922
923 if fork.peek(Token![<=]) {
924 fork.parse::<Token![<=]>()?;
925 let val = parse_predicate_value(&fork).map_err(|_| {
926 syn::Error::new(
927 fork.span(),
928 format!(
929 "invalid value after '<=' in predicate\n \
930 help: expected a numeric value, e.g., `{} <= 5` or `{} <= $threshold`",
931 signal_str, signal_str
932 ),
933 )
934 })?;
935
936 input.parse::<Ident>()?;
938 input.parse::<Token![<=]>()?;
939 let _: PredicateValue = parse_predicate_value(input)?;
940
941 return match val {
943 PredicateValue::Const(expr) => Ok(StlFormula::Not(Box::new(StlFormula::GreaterThan(
944 signal, expr,
945 )))),
946 PredicateValue::Var(var_ident) => Ok(StlFormula::Not(Box::new(
947 StlFormula::GreaterThanVar(signal, var_ident),
948 ))),
949 };
950 }
951
952 if fork.peek(Token![==]) {
953 fork.parse::<Token![==]>()?;
954 let val = parse_predicate_value(&fork).map_err(|_| {
955 syn::Error::new(
956 fork.span(),
957 format!(
958 "invalid value after '==' in predicate\n \
959 help: expected a numeric value, e.g., `{} == 5` or `{} == $threshold`",
960 signal_str, signal_str
961 ),
962 )
963 })?;
964
965 input.parse::<Ident>()?;
967 input.parse::<Token![==]>()?;
968 let _: PredicateValue = parse_predicate_value(input)?;
969
970 return match val {
973 PredicateValue::Const(expr) => {
974 let gte =
975 StlFormula::Not(Box::new(StlFormula::LessThan(signal.clone(), expr.clone())));
976 let lte = StlFormula::Not(Box::new(StlFormula::GreaterThan(signal, expr)));
977 Ok(StlFormula::And(Box::new(gte), Box::new(lte)))
978 }
979 PredicateValue::Var(var_ident) => {
980 let gte = StlFormula::Not(Box::new(StlFormula::LessThanVar(
981 signal.clone(),
982 var_ident.clone(),
983 )));
984 let lte = StlFormula::Not(Box::new(StlFormula::GreaterThanVar(signal, var_ident)));
985 Ok(StlFormula::And(Box::new(gte), Box::new(lte)))
986 }
987 };
988 }
989
990 Err(syn::Error::new(
991 signal.span(),
992 format!(
993 "expected comparison operator after signal '{}'\n \
994 help: predicates require a comparison: `{} > value`, `{} < value`, `{} >= value`, `{} <= value`, or `{} == value`",
995 signal_str, signal_str, signal_str, signal_str, signal_str, signal_str
996 ),
997 ))
998}
999
1000fn parse_predicate_value(input: ParseStream) -> Result<PredicateValue> {
1003 if input.peek(Token![$]) {
1008 input.parse::<Token![$]>()?;
1009 if !input.peek(Ident) {
1010 return Err(syn::Error::new(
1011 input.span(),
1012 "expected identifier after '$'\n \
1013 help: variable references should be written as `$variable_name`, e.g., `x > $threshold`",
1014 ));
1015 }
1016 let var_ident: Ident = input.parse()?;
1017 return Ok(PredicateValue::Var(var_ident));
1018 }
1019
1020 if input.peek(Token![-]) {
1022 let neg: Token![-] = input.parse()?;
1023 if input.peek(syn::LitInt) {
1024 let lit: syn::LitInt = input.parse()?;
1025 return Ok(PredicateValue::Const(
1026 syn::parse_quote_spanned!(neg.span=> -#lit),
1027 ));
1028 }
1029 if input.peek(syn::LitFloat) {
1030 let lit: syn::LitFloat = input.parse()?;
1031 return Ok(PredicateValue::Const(
1032 syn::parse_quote_spanned!(neg.span=> -#lit),
1033 ));
1034 }
1035 if input.peek(syn::token::Paren) {
1037 let content;
1038 parenthesized!(content in input);
1039 let inner: Expr = content.parse()?;
1040 return Ok(PredicateValue::Const(
1041 syn::parse_quote_spanned!(neg.span=> -(#inner)),
1042 ));
1043 }
1044 return Err(syn::Error::new(
1045 neg.span,
1046 "invalid negative value\n \
1047 help: expected a number after '-', e.g., `-5`, `-3.14`, or `-(expr)`",
1048 ));
1049 }
1050
1051 if input.peek(syn::LitInt) {
1053 let lit: syn::LitInt = input.parse()?;
1054 return Ok(PredicateValue::Const(Expr::Lit(syn::ExprLit {
1055 attrs: vec![],
1056 lit: syn::Lit::Int(lit),
1057 })));
1058 }
1059
1060 if input.peek(syn::LitFloat) {
1061 let lit: syn::LitFloat = input.parse()?;
1062 return Ok(PredicateValue::Const(Expr::Lit(syn::ExprLit {
1063 attrs: vec![],
1064 lit: syn::Lit::Float(lit),
1065 })));
1066 }
1067
1068 if input.peek(syn::token::Paren) {
1070 let content;
1071 parenthesized!(content in input);
1072 if content.is_empty() {
1073 return Err(content.error(
1074 "empty parentheses in value position\n \
1075 help: expected an expression inside parentheses, e.g., `(x + 1)`",
1076 ));
1077 }
1078 let inner: Expr = content.parse()?;
1079 return Ok(PredicateValue::Const(syn::parse_quote!((#inner))));
1080 }
1081
1082 if input.peek(Ident) {
1084 let ident: Ident = input.parse()?;
1085 let ident_str = ident.to_string();
1086
1087 if matches!(
1089 ident_str.as_str(),
1090 "and"
1091 | "or"
1092 | "implies"
1093 | "not"
1094 | "G"
1095 | "F"
1096 | "U"
1097 | "globally"
1098 | "eventually"
1099 | "until"
1100 ) {
1101 return Err(syn::Error::new(
1102 ident.span(),
1103 format!(
1104 "'{}' is an STL keyword and cannot be used as a value\n \
1105 help: did you forget the comparison value? e.g., `signal > 5 {} ...`",
1106 ident_str, ident_str
1107 ),
1108 ));
1109 }
1110
1111 return Ok(PredicateValue::Const(Expr::Path(syn::ExprPath {
1112 attrs: vec![],
1113 qself: None,
1114 path: ident.into(),
1115 })));
1116 }
1117
1118 Err(syn::Error::new(
1119 input.span(),
1120 "expected a value (number, variable, $var reference, or parenthesized expression)\n \
1121 help: valid values include: `5`, `3.14`, `-10`, `threshold`, `$threshold`, `(x + 1)`",
1122 ))
1123}