1use proc_macro::TokenStream;
3use quote::quote;
4use syn::{
5 Arm, Error, Expr, ExprClosure, ItemFn, LitStr, Result, Token,
6 parse::{Parse, ParseStream},
7 parse_macro_input,
8 punctuated::Punctuated,
9};
10mod nodes;
11use nodes::*;
12
13#[proc_macro_attribute]
33pub fn ifview(_attr: TokenStream, item: TokenStream) -> TokenStream {
34 let input = parse_macro_input!(item as ItemFn);
35
36 let name = &input.sig.ident;
37 let original_block = &input.block;
38
39 if input.sig.inputs.len() != 1 {
40 return Error::new_spanned(
41 &input.sig.inputs,
42 "ifview functions must have exactly one input: the context type C",
43 )
44 .to_compile_error()
45 .into();
46 }
47
48 let ctx_arg = input.sig.inputs.first().unwrap();
49 let ctx_type = if let syn::FnArg::Typed(pat_type) = ctx_arg
50 && let syn::Type::Reference(ty_ref) = &*pat_type.ty
51 && ty_ref.mutability.is_some()
52 {
53 &*ty_ref.elem
54 } else {
55 return Error::new_spanned(ctx_arg, "Expected a &mut C type")
56 .to_compile_error()
57 .into();
58 };
59
60 let expanded = quote! {
61 pub fn #name(__ifengine_game: &mut ifengine::Game<#ctx_type>)
62 -> ifengine::core::Response
63 {
64 let __ifengine_simulating = __ifengine_game.simulating();
65 #[allow(unused_variables)]
66 let #ctx_arg = &mut __ifengine_game.context;
67 let __ifengine_game_tags = &mut __ifengine_game.tags;
68 let __ifengine_game = &mut __ifengine_game.inner;
69 let mut __ifengine_page_state = ifengine::core::PageState::new(
70
71 format!("{}::{}", module_path!(), stringify!(#name)),
72 __ifengine_game.fresh(),
73 __ifengine_simulating,
74 __ifengine_game.state.get_page_mut(format!("{}::{}", module_path!(), stringify!(#name))),
75 __ifengine_game_tags,
76
77 );
78
79 #original_block
80
81 #[allow(unreachable_code)]
82 __ifengine_page_state.into_response()
83 }
84 };
85
86 expanded.into()
87}
88
89struct LineArm {
93 line: Expr,
94 block: Option<Expr>,
95}
96
97impl Parse for LineArm {
98 fn parse(input: ParseStream) -> Result<Self> {
99 let line: Expr = input.parse()?;
100
101 let block = if input.parse::<Token![=>]>().is_ok() {
102 Some(input.parse()?)
103 } else {
104 None
105 };
106
107 Ok(LineArm { line, block })
108 }
109}
110struct ChoiceInput {
111 maybe_key: MaybeKey,
112 arms: Vec<LineArm>,
113}
114
115impl Parse for ChoiceInput {
116 fn parse(input: ParseStream) -> Result<Self> {
117 let maybe_key = input.parse()?;
118
119 let mut arms = Vec::new();
120 while !input.is_empty() {
121 let mut lhs_exprs = vec![input.parse::<Expr>()?];
122
123 while input.peek(Token![|]) {
124 let _ = input.parse::<Token![|]>()?;
125 lhs_exprs.push(input.parse()?);
126 }
127
128 let block = if input.parse::<Token![=>]>().is_ok() {
129 Some(input.parse()?)
130 } else {
131 None
132 };
133
134 for line in lhs_exprs {
135 arms.push(LineArm {
136 line,
137 block: block.clone(),
138 });
139 }
140
141 input.parse::<Token![,]>().ok();
142 }
143
144 Ok(ChoiceInput { maybe_key, arms })
145 }
146}
147
148#[proc_macro]
176pub fn choice(input: TokenStream) -> TokenStream {
177 let ChoiceInput { maybe_key, arms } = syn::parse_macro_input!(input as ChoiceInput);
178
179 let key_tokens = maybe_key.into_tokens();
180
181 let mut index_arms = Vec::new();
182 let mut lines = Vec::new();
183
184 for (i, LineArm { line, block }) in arms.iter().enumerate() {
185 let i = i as u8;
186
187 lines.push(quote! { (#i, ifengine::view::Line::from(#line)) });
188
189 let block_tokens = match block {
190 Some(b) => quote! { ifengine::view::Line::from({ #b }) },
191 None => quote! { unreachable!() },
192 };
193
194 index_arms.push(quote! {
195 #i => { #block_tokens }
196 });
197 }
198
199 let expanded = quote! {
200 if let Some(__ifengine_tmp_idx) = __ifengine_page_state.get_mask_last(#key_tokens) {
201 #[allow(unreachable_code)]
202 __ifengine_page_state.push(
203 ifengine::view::Object::Paragraph(
204 match __ifengine_tmp_idx {
205 #(#index_arms),*,
206 _ => unreachable!(),
207 }
208 )
209 );
210 true
211 } else {
212 __ifengine_page_state.push(
213 ifengine::view::Object::Choice(
214 #key_tokens,
215 vec![
216 #(#lines),*
217 ]
218 )
219 );
220 false
221 }
222 };
223
224 expanded.into()
225}
226
227#[proc_macro]
253pub fn mchoice(input: TokenStream) -> TokenStream {
254 let ChoiceInput { maybe_key, arms } = syn::parse_macro_input!(input as ChoiceInput);
255
256 let key = maybe_key.into_tokens();
257
258 let arm_blocks: Vec<_> = arms
259 .iter()
260 .enumerate()
261 .map(|(i, LineArm { line, block })| {
262 let i = i as u8;
263
264 let block_tokens = match block {
265 Some(b) => quote! { #b },
266 None => quote! {},
267 };
268
269 quote! {
270 if (__ifengine_tmp_mask & (1u64 << #i)) != 0 {
271 #block_tokens
272 }
273 if let Some(l) = ifengine::elements::ChoiceVariant::from(#line)
274 .as_line((__ifengine_tmp_mask & (1u64 << #i)) != 0)
275 {
276 __ifengine_tmp_lines.push((#i, l));
277 __ifengine_visible_mask[#i as usize] = false;
278 }
279 }
280 })
281 .collect::<Vec<_>>();
282
283 let n = arms.len();
284
285 let expanded = quote! {
286 {
287 let __ifengine_tmp_mask = __ifengine_page_state.get(#key).unwrap_or(0u64);
288 let mut __ifengine_tmp_lines = Vec::new();
289 let mut __ifengine_visible_mask = [true; #n];
290
291 #(#arm_blocks)*
292
293 if ! __ifengine_tmp_lines.is_empty() {
294 __ifengine_page_state.push(
295 ifengine::view::Object::Choice(#key, __ifengine_tmp_lines)
296 );
297 }
298
299 __ifengine_visible_mask
300 }
301 };
302
303 expanded.into()
304}
305
306#[proc_macro]
369pub fn dynamic_choice(input: TokenStream) -> TokenStream {
370 let KeyExpr { maybe_key, expr } = syn::parse_macro_input!(input as KeyExpr);
371 let key_tokens = maybe_key.into_tokens();
372
373 let expanded = quote! {
374 {
375 __ifengine_page_state.push(ifengine::view::Object::Choice(
377 #key_tokens,
378 #expr
379 .into_iter()
380 .map(|(t, l)| (t as u8, ifengine::view::Line::from(l)))
381 .collect()
382 ));
383
384 __ifengine_page_state.remove_mask_last(#key_tokens).map(|x|
385 unsafe { std::mem::transmute::<u8, _>(x) }
386 )
387 }
388 };
389
390 expanded.into()
391}
392
393struct DChoicesInput {
394 pub maybe_key: MaybeKey,
395 pub expr: Expr,
396 pub arms: Vec<Arm>,
397}
398
399impl Parse for DChoicesInput {
400 fn parse(input: ParseStream) -> Result<Self> {
401 let KeyExpr { maybe_key, expr } = input.parse()?;
402
403 if !input.is_empty() {
404 input.parse::<Token![,]>()?;
405 }
406
407 let mut arms = Vec::new();
408 while !input.is_empty() {
409 arms.push(input.parse::<Arm>()?);
410 }
411
412 Ok(DChoicesInput {
413 maybe_key,
414 expr,
415 arms,
416 })
417 }
418}
419
420#[proc_macro]
440pub fn dchoice(input: TokenStream) -> TokenStream {
441 let DChoicesInput {
442 maybe_key,
443 expr,
444 arms,
445 } = parse_macro_input!(input as DChoicesInput);
446
447 let key_tokens = maybe_key.into_tokens();
448
449 let has_wildcard = arms.iter().any(|arm| matches!(arm.pat, syn::Pat::Wild(_)));
450 let catch_all = if has_wildcard {
451 quote! {}
452 } else {
453 quote! { _ => {} }
454 };
455 let match_block = if arms.is_empty() {
456 quote! {}
457 } else {
458 quote! {
459 if let Some(__ifengine_id) = __ifengine_page_state.remove_mask_last(#key_tokens) {
460 match __ifengine_id as usize {
461 #(#arms)*
462 #catch_all
463 }
464 }
465 }
466 };
467
468 let expanded = quote! {
469 {
470 __ifengine_page_state.push(ifengine::view::Object::Choice(
471 #key_tokens,
472 #expr
473 .iter()
474 .enumerate()
475 .map(|(i, l)| (i as u8, ifengine::view::Line::from(l.clone())))
476 .collect()
477 ));
478
479 if let Some(__ifengine_id) = __ifengine_page_state.remove_mask_last(#key_tokens) {
480 #match_block
481 }
482 }
483 };
484
485 expanded.into()
486}
487#[proc_macro]
501pub fn dparagraph(input: TokenStream) -> TokenStream {
502 let KeyExprs { maybe_key, exprs } = syn::parse_macro_input!(input as KeyExprs);
503
504 let key = maybe_key.into_tokens();
505
506 let expanded = quote! {{
507 let mut ret = None;
508
509 #(
510 let mut __ifengine_tmp_strings =
511 ifengine::utils::split_braced(&ifengine::utils::trim_lines(&#exprs));
512
513 if let Some(__ifengine_tmp_val) = __ifengine_page_state
514 .remove(#key)
515 .and_then(|k| {
516 ifengine::utils::find_hash_match(__ifengine_tmp_strings.iter().step_by(2), k).cloned()
517 }) {
518 ret = Some(__ifengine_tmp_val);
519 }
520
521 __ifengine_page_state.push(
522 ifengine::view::Object::Paragraph(
523 ifengine::view::Line::from_interleaved_actions::<false>(
524 (__ifengine_page_state.id(), #key),
525 __ifengine_tmp_strings
526 )
527 )
528 );
529 )*
530
531 ret
532 }};
533
534 expanded.into()
535}
536
537#[proc_macro]
546pub fn mparagraph(input: TokenStream) -> TokenStream {
547 let KeyExpr { maybe_key, expr } = syn::parse_macro_input!(input as KeyExpr);
548
549 let key = maybe_key.into_tokens();
550
551 let expanded = quote! {{
552 let strings =
553 ifengine::utils::split_braced(&ifengine::utils::trim_lines(&#expr));
554 let count = strings.len() / 2;
555
556 __ifengine_page_state.push(
557 ifengine::view::Object::Paragraph(
558 ifengine::view::Line::from_interleaved_actions::<true>(
559 (__ifengine_page_state.id(), #key),
560 strings
561 )
562 )
563 );
564
565 __ifengine_page_state.get_mask::<64>(#key)[..count].to_vec()
566 }};
567
568 expanded.into()
569}
570
571#[proc_macro]
575pub fn push(input: TokenStream) -> TokenStream {
576 let expr = parse_macro_input!(input as Expr);
577
578 let expanded = quote! {
579 __ifengine_page_state.push(
580 #expr
581 );
582 };
583
584 expanded.into()
585}
586
587struct LineArgs {
588 exprs: Vec<Expr>,
589 trailer: Option<LitStr>,
590}
591
592impl syn::parse::Parse for LineArgs {
593 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
594 let mut exprs = Vec::new();
595 let mut trailer = None;
596
597 while !input.is_empty() {
598 if input.peek(Token![::]) {
599 let _coloncolon: Token![::] = input.parse()?;
600 let lit: LitStr = input.parse()?;
601 trailer = Some(lit);
602 break;
603 }
604
605 exprs.push(input.parse()?);
606
607 if input.peek(Token![,]) {
608 let _ = input.parse::<Token![,]>()?;
609 } else {
610 break;
611 }
612 }
613
614 Ok(LineArgs { exprs, trailer })
615 }
616}
617
618#[proc_macro]
629pub fn text(input: TokenStream) -> TokenStream {
630 let LineArgs { exprs, trailer } = syn::parse_macro_input!(input as LineArgs);
631
632 let string_expr = match trailer {
633 Some(s) => quote!(#s),
634 None => quote!(""),
635 };
636
637 let expanded = quote! {
638 __ifengine_page_state.push(
639 ifengine::view::Object::Text(
640 ifengine::view::Line::from_spans(
641 vec![#(#exprs.into()),*]
642 ),
643 #string_expr
644 )
645 );
646 };
647
648 TokenStream::from(expanded)
649}
650
651#[proc_macro]
664pub fn texts(input: TokenStream) -> TokenStream {
665 let LineArgs { exprs, trailer } = syn::parse_macro_input!(input as LineArgs);
666
667 let string_expr = match trailer {
668 Some(s) => quote!(#s),
669 None => quote!(""),
670 };
671
672 let expanded = quote! {
673 #(
674 __ifengine_page_state.push(
675 ifengine::view::Object::Text(
676 ifengine::view::Line::from(#exprs),
677 #string_expr
678 )
679 );
680 )*
681 };
682
683 TokenStream::from(expanded)
684}
685
686#[proc_macro]
693pub fn paragraph(input: TokenStream) -> TokenStream {
694 let exprs_parsed = parse_macro_input!(input with Punctuated<Expr, Token![,]>::parse_terminated);
695 let exprs: Vec<Expr> = exprs_parsed.into_iter().collect();
696
697 let expanded = quote! {
698 __ifengine_page_state.push(
699 ifengine::view::Object::Paragraph(
700 ifengine::view::Line::from_spans(vec![#(ifengine::view::Span::from_lingual(#exprs)),*])
701 )
702 );
703 };
704
705 TokenStream::from(expanded)
706}
707
708#[proc_macro]
718pub fn paragraphs(input: TokenStream) -> TokenStream {
719 use quote::quote;
720 use syn::punctuated::Punctuated;
721 use syn::{Expr, Token, parse_macro_input};
722
723 let exprs_parsed = parse_macro_input!(input with Punctuated<Expr, Token![,]>::parse_terminated);
724 let exprs: Vec<Expr> = exprs_parsed.into_iter().collect();
725
726 let expanded = quote! {
727 #(
728 __ifengine_page_state.push(
729 ifengine::view::Object::Paragraph(
730 ifengine::view::Line::from_lingual(#exprs)
731 )
732 );
733 )*
734 };
735
736 TokenStream::from(expanded)
737}
738
739#[proc_macro]
747pub fn img(input: TokenStream) -> TokenStream {
748 use quote::quote;
749 use syn::punctuated::Punctuated;
750 use syn::{Expr, Lit, Token, parse_macro_input};
751
752 let exprs_parsed = parse_macro_input!(input with Punctuated<Expr, Token![,]>::parse_terminated);
753 let exprs: Vec<&Expr> = exprs_parsed.iter().collect();
754
755 let (path_expr, size_expr) = match exprs.len() {
756 1 => (exprs[0], None),
757 2 => (exprs[0], Some(exprs[1])),
758 _ => {
759 return syn::Error::new_spanned(exprs_parsed, "image! macro expects 1 or 2 arguments")
760 .to_compile_error()
761 .into();
762 }
763 };
764
765 let image_tokens = if let Expr::Lit(lit) = path_expr
766 && let Lit::Str(s) = &lit.lit
767 {
768 let path = s.value();
769 if path.starts_with("http://") || path.starts_with("https://") {
770 if let Some(size) = size_expr {
771 quote! { ifengine::view::Image::new_url(#path).with_size(#size) }
772 } else {
773 quote! { ifengine::view::Image::new_url(#path) }
774 }
775 } else {
776 if let Some(size) = size_expr {
777 quote! { ifengine::view::Image::new_local(#path, include_bytes!(#path)).with_size(#size) }
778 } else {
779 quote! { ifengine::view::Image::new_local(#path, include_bytes!(#path)) }
780 }
781 }
782 } else {
783 return syn::Error::new_spanned(path_expr, "expected string literal")
784 .to_compile_error()
785 .into();
786 };
787
788 let expanded = quote! {
789 __ifengine_page_state.push(ifengine::view::Object::Image(#image_tokens));
790 };
791
792 TokenStream::from(expanded)
793}
794
795#[proc_macro]
802pub fn h(input: TokenStream) -> TokenStream {
803 let exprs_parsed = parse_macro_input!(input with Punctuated<Expr, Token![,]>::parse_terminated);
804 let exprs: Vec<&Expr> = exprs_parsed.iter().collect();
805
806 if exprs.len() != 2 {
807 return syn::Error::new_spanned(
808 exprs_parsed,
809 "macro expects exactly 2 arguments: text and level",
810 )
811 .to_compile_error()
812 .into();
813 }
814
815 let text = exprs[0];
816 let level = exprs[1];
817
818 let expanded = quote! {
819 __ifengine_page_state.push(
820 ifengine::view::Object::Heading(ifengine::view::Span::from_lingual(#text), #level)
821 );
822 };
823
824 TokenStream::from(expanded)
825}
826
827#[proc_macro]
834pub fn hr(_input: TokenStream) -> TokenStream {
835 let expanded = quote! {
836 __ifengine_page_state.push(ifengine::view::Object::Break);
837 };
838
839 TokenStream::from(expanded)
840}
841
842#[derive(Clone)]
845enum AltVariant {
846 Stop,
847 Shuffle,
848 Cycle,
849}
850
851impl Default for AltVariant {
852 fn default() -> Self {
853 AltVariant::Stop
854 }
855}
856
857impl Parse for AltVariant {
858 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
859 let ident: syn::Ident = input.parse()?;
860 match ident.to_string().as_str() {
861 "Stop" => Ok(AltVariant::Stop),
862 "Shuffle" => Ok(AltVariant::Shuffle),
863 "Cycle" => Ok(AltVariant::Cycle),
864 _ => Err(syn::Error::new(
865 ident.span(),
866 "expected AltVariant: Stop | Shuffle | Cycle",
867 )),
868 }
869 }
870}
871
872struct AltsInput {
873 maybe_key: MaybeKey,
874 list: Vec<Expr>,
875 variant: Option<AltVariant>,
876}
877
878impl Parse for AltsInput {
879 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
880 let content;
881 syn::bracketed!(content in input);
882
883 let maybe_key = input.parse()?;
884
885 let mut list = Vec::new();
886 while !content.is_empty() {
887 list.push(content.parse()?);
888 if content.peek(Token![,]) {
889 let _: Token![,] = content.parse()?;
890 }
891 }
892
893 let variant = if !input.is_empty() {
895 input.parse::<Token![,]>()?;
896 Some(input.parse()?)
897 } else {
898 None
899 };
900
901 Ok(Self {
902 maybe_key,
903 list,
904 variant,
905 })
906 }
907}
908
909#[proc_macro]
979pub fn alts(input: TokenStream) -> TokenStream {
980 let AltsInput {
981 maybe_key,
982 list,
983 variant,
984 } = parse_macro_input!(input as AltsInput);
985
986 let key = maybe_key.into_tokens();
987
988 let variant = variant.unwrap_or_default();
989 let list_init = quote! { &[ #(#list),* ] };
990
991 let expanded = match variant {
992 AltVariant::Stop => {
993 quote! {{
994 let alts = #list_init;
995
996 if let Some(idx) = __ifengine_page_state.get(#key) {
997 ifengine::view::Span::from(
998 alts[(idx as usize + 1).min(alts.len() - 1)]
999 )
1000 .with_action(ifengine::Action::Inc((__ifengine_page_state.id(), #key)))
1001 } else {
1002 ifengine::view::Span::from(
1003 alts[0]
1004 )
1005 .with_action(ifengine::Action::Inc((__ifengine_page_state.id(), #key)))
1006 }
1007 }}
1008 }
1009
1010 AltVariant::Shuffle => {
1011 quote! {{
1012 let alts = #list_init;
1013
1014 let idx = if let Some(prev) = __ifengine_page_state.get(#key) {
1016 if prev & 1 == 0 {
1017 (prev as usize) >> 1
1018 } else {
1019 let new_idx = __ifengine_page_state.rand(alts.len(), &[(prev as usize) >> 1]);
1021 __ifengine_page_state.insert(#key, (new_idx as u64) << 1);
1022 new_idx
1023 }
1024 } else {
1025 let new_idx = __ifengine_page_state.rand(alts.len(), &[]);
1026 __ifengine_page_state.insert(#key, (new_idx as u64) << 1);
1027 new_idx
1028 } ;
1029
1030 ifengine::view::Span::from(alts[idx])
1032 .with_action(ifengine::Action::Set(
1033 (__ifengine_page_state.id(), #key),
1034 ((idx as u64) << 1) + 1
1035 ))
1036 .no_sim()
1037 }}
1038 }
1039
1040 AltVariant::Cycle => {
1041 quote! {{
1042 let alts = #list_init;
1043
1044 if let Some(idx) = __ifengine_page_state.get(#key) {
1045 ifengine::view::Span::from(
1046 alts[(idx as usize) % alts.len()]
1047 )
1048 .with_action(ifengine::Action::Inc((__ifengine_page_state.id(), #key)))
1049 .no_sim()
1050 } else {
1051 ifengine::view::Span::from(
1052 alts[0]
1053 )
1054 .with_action(ifengine::Action::Inc((__ifengine_page_state.id(), #key)))
1055 .no_sim()
1056 }
1057 }}
1058 }
1059 };
1060
1061 expanded.into()
1062}
1063struct CountInput {
1068 maybe_key: MaybeKey,
1069 closure: ExprClosure,
1070}
1071
1072impl Parse for CountInput {
1073 fn parse(input: ParseStream) -> Result<Self> {
1074 let maybe_key = input.parse()?;
1075 let closure = input.parse()?;
1076
1077 Ok(CountInput { maybe_key, closure })
1078 }
1079}
1080
1081#[proc_macro]
1093pub fn count(input: TokenStream) -> TokenStream {
1094 let CountInput { maybe_key, closure } = syn::parse_macro_input!(input as CountInput);
1095 let key = maybe_key.into_tokens();
1096
1097 let expanded = quote! {{
1098 ifengine::view::Span::from(
1099 (#closure)(__ifengine_page_state.get(#key).unwrap_or_default())
1100 )
1101 .with_action(ifengine::Action::Inc((__ifengine_page_state.id(), #key)))
1102 .no_sim()
1103 }};
1104
1105 expanded.into()
1106}
1107
1108struct ClickInput {
1109 maybe_key: MaybeKey,
1110 expr: Expr,
1111 block: Expr,
1112}
1113
1114impl Parse for ClickInput {
1115 fn parse(input: ParseStream) -> Result<Self> {
1116 let maybe_key = input.parse()?;
1117
1118 let expr: Expr = input.parse()?;
1119
1120 let block = if input.peek(Token![,]) {
1121 input.parse::<Token![,]>()?;
1122 input.parse::<Expr>()?
1123 } else {
1124 syn::parse_quote!({})
1125 };
1126
1127 Ok(ClickInput {
1128 maybe_key,
1129 expr,
1130 block,
1131 })
1132 }
1133}
1134
1135#[proc_macro]
1150pub fn click(input: TokenStream) -> TokenStream {
1151 let ClickInput {
1152 maybe_key,
1153 expr,
1154 block,
1155 } = syn::parse_macro_input!(input as ClickInput);
1156 let key = maybe_key.into_tokens();
1157
1158 let expanded = quote! {{
1159 if __ifengine_page_state.was_zero(#key) {
1160 let _ = #block;
1161 };
1162
1163 let span = ifengine::view::Span::from(
1164 #expr
1165 )
1166 .with_action(ifengine::Action::Inc((__ifengine_page_state.id(), #key)))
1167 .as_link();
1168
1169 if __ifengine_page_state.get(#key).is_some() {
1171 span.no_sim()
1172 } else {
1173 span
1174 }
1175 }};
1176
1177 expanded.into()
1178}
1179
1180#[proc_macro]
1187pub fn fresh(input: TokenStream) -> TokenStream {
1188 let closure = parse_macro_input!(input as ExprClosure);
1189
1190 let expanded = quote! {{
1191 if __ifengine_page_state.fresh() {
1192 (#closure)();
1193 }
1194 }};
1195
1196 expanded.into()
1197}
1198
1199#[proc_macro]
1209pub fn back(input: TokenStream) -> TokenStream {
1210 let ExprAndOptional { expr, n } = parse_macro_input!(input as ExprAndOptional);
1211
1212 let expanded = if let Some(n_expr) = n {
1213 quote! {
1214 ifengine::view::Span::from(#expr)
1215 .as_link()
1216 .with_action(ifengine::Action::Back(#n_expr))
1217 }
1218 } else {
1219 quote! {
1220 ifengine::view::Span::from(#expr)
1221 .as_link()
1222 .with_action(ifengine::Action::Back(1))
1223 .no_sim()
1224 }
1225 };
1226
1227 TokenStream::from(expanded)
1228}
1229
1230#[proc_macro]
1234#[allow(non_snake_case)]
1235pub fn r#YIELD(_input: TokenStream) -> TokenStream {
1236 let expanded = quote! {
1237 return __ifengine_page_state.into_response()
1238 };
1239 expanded.into()
1240}
1241
1242#[proc_macro]
1255pub fn read_key(input: TokenStream) -> TokenStream {
1256 let expr = syn::parse_macro_input!(input as syn::Expr);
1257
1258 let expanded = quote! {
1259 __ifengine_page_state.get(#expr)
1260 };
1261
1262 expanded.into()
1263}
1264
1265#[proc_macro]
1273pub fn read_key_mask(input: TokenStream) -> TokenStream {
1274 let ExprAndOptional { expr: key, n } = syn::parse_macro_input!(input as ExprAndOptional);
1275
1276 let n = n.unwrap_or_else(|| syn::parse_quote!(64));
1277
1278 quote! {
1279 __ifengine_page_state.get_mask::<#n>(#key)
1280 }
1281 .into()
1282}
1283
1284#[proc_macro]
1291pub fn set_key(input: TokenStream) -> TokenStream {
1292 let expr = syn::parse_macro_input!(input as syn::Expr);
1293
1294 let expanded = quote! {
1295 __ifengine_page_state.insert(#expr.0, #expr.1)
1296 };
1297
1298 expanded.into()
1299}
1300
1301#[proc_macro]
1308pub fn set_key_mask(input: TokenStream) -> TokenStream {
1309 use syn::{Expr, Token, parse::Parser, punctuated::Punctuated};
1310
1311 let parts = match Punctuated::<Expr, Token![,]>::parse_terminated.parse(input) {
1312 Ok(parts) => parts,
1313 Err(e) => return e.to_compile_error().into(),
1314 };
1315
1316 let mut iter = parts.iter();
1317 let key = if let Some(key) = iter.next() {
1318 key
1319 } else {
1320 return syn::Error::new_spanned(parts, "expected key")
1321 .to_compile_error()
1322 .into();
1323 };
1324 let bits: Vec<&Expr> = iter.collect();
1325
1326 let mut mask = 0u64;
1327 for expr in &bits {
1328 if let Expr::Lit(syn::ExprLit {
1329 lit: syn::Lit::Int(i),
1330 ..
1331 }) = expr
1332 {
1333 match i.base10_parse::<usize>() {
1334 Ok(bit) => mask |= 1u64 << bit,
1335 Err(_) => {
1336 return syn::Error::new_spanned(i, "failed to parse bit position")
1337 .to_compile_error()
1338 .into();
1339 }
1340 }
1341 } else {
1342 return syn::Error::new_spanned(expr, "bit positions must be integer literals")
1343 .to_compile_error()
1344 .into();
1345 }
1346 }
1347
1348 let expanded = quote! {
1349 {
1350 let old = __ifengine_page_state.get(#key).unwrap_or(0u64);
1351 __ifengine_page_state.insert(#key, old | #mask);
1352 }
1353 };
1354
1355 expanded.into()
1356}
1357
1358#[proc_macro]
1365pub fn unset_key_mask(input: TokenStream) -> TokenStream {
1366 use syn::{Expr, Token, parse::Parser, punctuated::Punctuated};
1367
1368 let parts = match Punctuated::<Expr, Token![,]>::parse_terminated.parse(input) {
1369 Ok(parts) => parts,
1370 Err(e) => return e.to_compile_error().into(),
1371 };
1372
1373 let mut iter = parts.iter();
1374 let key = if let Some(key) = iter.next() {
1375 key
1376 } else {
1377 return syn::Error::new_spanned(parts, "expected key")
1378 .to_compile_error()
1379 .into();
1380 };
1381 let bits: Vec<&Expr> = iter.collect();
1382
1383 let mut mask = 0u64;
1384 for expr in &bits {
1385 if let Expr::Lit(syn::ExprLit {
1386 lit: syn::Lit::Int(i),
1387 ..
1388 }) = expr
1389 {
1390 match i.base10_parse::<usize>() {
1391 Ok(bit) => mask |= 1u64 << bit,
1392 Err(_) => {
1393 return syn::Error::new_spanned(i, "failed to parse bit position")
1394 .to_compile_error()
1395 .into();
1396 }
1397 }
1398 } else {
1399 return syn::Error::new_spanned(expr, "bit positions must be integer literals")
1400 .to_compile_error()
1401 .into();
1402 }
1403 }
1404
1405 let expanded = quote! {
1406 {
1407 let old = __ifengine_page_state.get(#key).unwrap_or(0u64);
1408 __ifengine_page_state.insert(#key, old & !#mask);
1409 }
1410 };
1411
1412 expanded.into()
1413}
1414
1415#[proc_macro]
1422pub fn inc_key(input: TokenStream) -> TokenStream {
1423 let expr = syn::parse_macro_input!(input as syn::Expr);
1424
1425 let expanded = quote! {
1426 {
1427 let k = #expr;
1428 let v = __ifengine_page_state.get(k).unwrap_or(0);
1429 __ifengine_page_state.insert(k, v.wrapping_add(1));
1430 }
1431 };
1432
1433 expanded.into()
1434}
1435
1436#[proc_macro]
1443pub fn reset_key(input: TokenStream) -> TokenStream {
1444 let expr = syn::parse_macro_input!(input as syn::Expr);
1445
1446 let expanded = quote! {
1447 __ifengine_page_state.remove(#expr)
1448 };
1449
1450 expanded.into()
1451}
1452
1453#[proc_macro]
1468pub fn tag(input: TokenStream) -> TokenStream {
1469 use quote::quote;
1470 use syn::parse::{Parse, ParseStream, Result};
1471 use syn::{Expr, Ident, Token, parse_macro_input};
1472
1473 struct TagInput {
1474 expr: Expr,
1475 mode: Option<Ident>,
1476 }
1477
1478 impl Parse for TagInput {
1479 fn parse(input: ParseStream) -> Result<Self> {
1480 let expr: Expr = input.parse()?;
1481 let mode: Option<Ident> = if input.peek(Token![,]) {
1482 input.parse::<Token![,]>()?;
1483 Some(input.parse()?)
1484 } else if !input.is_empty() {
1485 Some(input.parse()?)
1486 } else {
1487 None
1488 };
1489 Ok(TagInput { expr, mode })
1490 }
1491 }
1492
1493 let TagInput { expr, mode } = parse_macro_input!(input as TagInput);
1494
1495 let sticky = match mode {
1496 Some(id) => match id.to_string().as_str() {
1497 "Sticky" => true,
1498 "Once" => false,
1499 _ => {
1500 return syn::Error::new_spanned(&id, "Expected `Sticky` or `Once`")
1501 .to_compile_error()
1502 .into();
1503 }
1504 },
1505 None => false,
1506 };
1507
1508 let expanded = quote! {
1509 __ifengine_page_state.tag(#expr, #sticky)
1510 };
1511
1512 expanded.into()
1513}
1514
1515#[proc_macro]
1517pub fn untag(input: TokenStream) -> TokenStream {
1518 let expr = syn::parse_macro_input!(input as syn::Expr);
1519
1520 let expanded = quote! {
1521 __ifengine_page_state.untag(#expr)
1522 };
1523
1524 expanded.into()
1525}
1526
1527#[proc_macro]
1529pub fn in_sim(_: TokenStream) -> TokenStream {
1530 let expanded = quote! {
1531 __ifengine_page_state.simulating
1532 };
1533
1534 expanded.into()
1535}
1536
1537#[proc_macro]
1541pub fn page_dbg(_input: TokenStream) -> TokenStream {
1542 let expanded = quote! {
1543 dbg!(&__ifengine_page_state)
1545 };
1546 expanded.into()
1547}
1548
1549#[proc_macro]
1551pub fn view_dbg(_input: TokenStream) -> TokenStream {
1552 let expanded = quote! {
1553 dbg!(&__ifengine_page_state.view)
1554 };
1555 expanded.into()
1556}