krabka_protocol_codegen/emit/
common.rs1use std::str::FromStr;
2
3use proc_macro2::{Delimiter, Literal, TokenStream, TokenTree};
4use quote::quote;
5use syn::parse::Parse;
6
7use crate::{emit::EmitError, ir::FieldSpec};
8
9pub fn parse_tokens(s: &str) -> Result<TokenStream, EmitError> {
15 TokenStream::from_str(s).map_err(|e| EmitError::Lex {
16 fragment: s.to_owned(),
17 message: e.to_string(),
18 })
19}
20
21pub fn validate<T: Parse>(tokens: &TokenStream, what: &'static str) -> Result<T, EmitError> {
27 syn::parse2(tokens.clone()).map_err(|source| EmitError::Syntax { what, source })
28}
29
30#[must_use]
33pub fn statement(expr: &TokenStream) -> TokenStream {
34 let block_like = expr
35 .clone()
36 .into_iter()
37 .next()
38 .is_some_and(|first| match first {
39 TokenTree::Ident(ident) => ident == "if" || ident == "match" || ident == "for",
40 TokenTree::Group(group) => group.delimiter() == Delimiter::Brace,
41 TokenTree::Punct(_) | TokenTree::Literal(_) => false,
42 });
43 if block_like {
44 expr.clone()
45 } else {
46 quote!(#expr;)
47 }
48}
49
50#[must_use]
52pub fn uses_ident(tokens: &TokenStream, name: &str) -> bool {
53 tokens.clone().into_iter().any(|tree| match tree {
54 TokenTree::Ident(ident) => ident == name,
55 TokenTree::Group(group) => uses_ident(&group.stream(), name),
56 TokenTree::Punct(_) | TokenTree::Literal(_) => false,
57 })
58}
59
60pub fn tag_literal(f: &FieldSpec) -> Result<Literal, EmitError> {
65 f.tag
66 .map(Literal::u32_unsuffixed)
67 .ok_or_else(|| EmitError::MissingTag(f.name.clone()))
68}
69
70#[must_use]
72pub fn banner(schemas_version: &str) -> String {
73 format!("{BANNER_PREFIX} against {schemas_version}. Do not edit.\n")
74}
75
76pub const BANNER_PREFIX: &str = "// AUTO-GENERATED by krabka-protocol-codegen";
81
82#[must_use]
90pub fn format_int_literal(s: &str, suffix: &str) -> String {
91 let (negative_in, s) = s.strip_prefix('-').map_or((false, s), |rest| (true, rest));
94 let s = s.trim();
95 let decimal_string: String;
97 let (negative, digits): (bool, &str) =
98 if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
99 if let Ok(v) = u64::from_str_radix(hex, 16) {
103 let signed = hex_to_signed(v, suffix);
104 if negative_in || signed < 0 {
105 decimal_string = format!("{}", signed.unsigned_abs());
106 (true, &decimal_string)
107 } else {
108 decimal_string = format!("{signed}");
109 (false, &decimal_string)
110 }
111 } else {
112 decimal_string = s.to_owned();
114 (negative_in, &decimal_string)
115 }
116 } else {
117 decimal_string = s.to_owned();
118 (negative_in, &decimal_string)
119 };
120 let underscored: String = {
121 let chars: Vec<char> = digits.chars().collect();
122 let mut result = String::new();
123 let len = chars.len();
124 for (i, &c) in chars.iter().enumerate() {
125 if i > 0 && (len - i).is_multiple_of(3) {
126 result.push('_');
127 }
128 result.push(c);
129 }
130 result
131 };
132 if negative {
133 format!("-{underscored}{suffix}")
134 } else {
135 format!("{underscored}{suffix}")
136 }
137}
138
139fn hex_to_signed(v: u64, suffix: &str) -> i64 {
144 let bits: u32 = suffix.trim_start_matches(['i', 'u']).parse().unwrap_or(64);
145 if bits >= 64 {
146 return v.cast_signed();
147 }
148 let mask = (1u64 << bits) - 1;
149 let x = v & mask;
150 if suffix.starts_with('i') && (x & (1u64 << (bits - 1))) != 0 {
151 let shift = 64 - bits;
152 (x << shift).cast_signed() >> shift
153 } else {
154 x.cast_signed()
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use assert2::assert;
163
164 use super::*;
165
166 #[test]
167 fn decimal_literals() {
168 for (input, want) in [
169 ("0", "0i32"),
170 ("-1", "-1i32"),
171 ("2147483647", "2_147_483_647i32"),
172 ("-2147483648", "-2_147_483_648i32"),
173 ] {
174 assert!(format_int_literal(input, "i32") == want);
175 }
176 }
177
178 #[test]
179 fn hex_positive_normalizes_to_decimal() {
180 assert!(format_int_literal("0x7fffffff", "i32") == "2_147_483_647i32");
181 assert!(format_int_literal("0X10", "i32") == "16i32");
182 }
183
184 #[test]
185 fn hex_high_bit_is_sign_extended_to_target_width() {
186 for (input, suffix, want) in [
189 ("0xffffffff", "i32", "-1i32"),
190 ("0x80000000", "i32", "-2_147_483_648i32"),
191 ("0xffffffff", "i64", "4_294_967_295i64"),
192 ] {
193 assert!(format_int_literal(input, suffix) == want);
194 }
195 assert!(hex_to_signed(0x4000_0000_0000_0000, "i63") == -(1i64 << 62));
196 }
197}