1use proc_macro::TokenStream;
43use proc_macro2::TokenTree;
44use quote::{format_ident, quote};
45use std::collections::hash_map::DefaultHasher;
46use std::fmt::Write as FmtWrite;
47use std::hash::{Hash, Hasher};
48use std::str::FromStr;
49
50#[derive(Clone, PartialEq)]
54enum CsharpType {
55 Sbyte,
57 Short,
58 Int,
59 Long,
60 Byte,
62 Ushort,
63 Uint,
64 Ulong,
65 Float,
67 Double,
68 Bool,
70 Char,
71 Str,
72 Array(Box<CsharpType>),
75 List(Box<CsharpType>),
77 Nullable(Box<CsharpType>),
79}
80
81impl CsharpType {
82 fn from_name(s: &str) -> Option<Self> {
84 match s {
85 "sbyte" | "SByte" => Some(Self::Sbyte),
86 "byte" | "Byte" => Some(Self::Byte),
87 "short" | "Int16" => Some(Self::Short),
88 "ushort" | "UInt16" => Some(Self::Ushort),
89 "int" | "Int32" => Some(Self::Int),
90 "uint" | "UInt32" => Some(Self::Uint),
91 "long" | "Int64" => Some(Self::Long),
92 "ulong" | "UInt64" => Some(Self::Ulong),
93 "float" | "Single" => Some(Self::Float),
94 "double" | "Double" => Some(Self::Double),
95 "bool" | "Boolean" => Some(Self::Bool),
96 "char" | "Char" => Some(Self::Char),
97 "string" | "String" => Some(Self::Str),
98 _ => None,
99 }
100 }
101
102 fn csharp_type_name(&self) -> String {
104 match self {
105 Self::Sbyte => "sbyte".to_string(),
106 Self::Byte => "byte".to_string(),
107 Self::Short => "short".to_string(),
108 Self::Ushort => "ushort".to_string(),
109 Self::Int => "int".to_string(),
110 Self::Uint => "uint".to_string(),
111 Self::Long => "long".to_string(),
112 Self::Ulong => "ulong".to_string(),
113 Self::Float => "float".to_string(),
114 Self::Double => "double".to_string(),
115 Self::Bool => "bool".to_string(),
116 Self::Char => "char".to_string(),
117 Self::Str => "string".to_string(),
118 Self::Array(inner) => format!("{}[]", inner.csharp_type_name()),
119 Self::List(inner) => {
120 format!(
121 "System.Collections.Generic.List<{}>",
122 inner.csharp_type_name()
123 )
124 }
125 Self::Nullable(inner) => format!("{}?", inner.csharp_type_name()),
126 }
127 }
128
129 fn is_value_type(&self) -> bool {
133 matches!(
134 self,
135 Self::Sbyte
136 | Self::Byte | Self::Short
137 | Self::Ushort
138 | Self::Int | Self::Uint
139 | Self::Long | Self::Ulong
140 | Self::Float
141 | Self::Double
142 | Self::Bool | Self::Char
143 )
144 }
145
146 fn rust_return_type_ts(&self) -> proc_macro2::TokenStream {
148 match self {
149 Self::Sbyte => quote! { i8 },
150 Self::Byte => quote! { u8 },
151 Self::Short => quote! { i16 },
152 Self::Ushort => quote! { u16 },
153 Self::Int => quote! { i32 },
154 Self::Uint => quote! { u32 },
155 Self::Long => quote! { i64 },
156 Self::Ulong => quote! { u64 },
157 Self::Float => quote! { f32 },
158 Self::Double => quote! { f64 },
159 Self::Bool => quote! { bool },
160 Self::Char => quote! { char },
161 Self::Str => quote! { ::std::string::String },
162 Self::Array(inner) | Self::List(inner) => {
163 let inner_ts = inner.rust_return_type_ts();
164 quote! { ::std::vec::Vec<#inner_ts> }
165 }
166 Self::Nullable(inner) => {
167 let inner_ts = inner.rust_return_type_ts();
168 quote! { ::std::option::Option<#inner_ts> }
169 }
170 }
171 }
172
173 fn rust_param_type_ts(&self) -> proc_macro2::TokenStream {
176 match self {
177 Self::Str => quote! { &str },
178 Self::Array(inner) | Self::List(inner) => {
179 let inner_ts = inner.rust_param_type_ts();
180 quote! { &[#inner_ts] }
181 }
182 Self::Nullable(inner) => {
183 let inner_ts = inner.rust_param_type_ts();
184 quote! { ::std::option::Option<#inner_ts> }
185 }
186 _ => self.rust_return_type_ts(),
188 }
189 }
190
191 fn rust_ser_ts(
193 &self,
194 param_ident: &proc_macro2::TokenStream,
195 depth: usize,
196 ) -> proc_macro2::TokenStream {
197 match self {
198 Self::Sbyte => quote! {
199 _stdin_bytes.extend_from_slice(&(#param_ident as i8).to_le_bytes());
200 },
201 Self::Byte | Self::Bool => quote! {
202 _stdin_bytes.push(#param_ident as u8);
203 },
204 Self::Short => quote! {
205 _stdin_bytes.extend_from_slice(&(#param_ident as i16).to_le_bytes());
206 },
207 Self::Ushort => quote! {
208 _stdin_bytes.extend_from_slice(&(#param_ident as u16).to_le_bytes());
209 },
210 Self::Int => quote! {
211 _stdin_bytes.extend_from_slice(&(#param_ident as i32).to_le_bytes());
212 },
213 Self::Uint => quote! {
214 _stdin_bytes.extend_from_slice(&(#param_ident as u32).to_le_bytes());
215 },
216 Self::Long => quote! {
217 _stdin_bytes.extend_from_slice(&(#param_ident as i64).to_le_bytes());
218 },
219 Self::Ulong => quote! {
220 _stdin_bytes.extend_from_slice(&(#param_ident as u64).to_le_bytes());
221 },
222 Self::Float => quote! {
223 _stdin_bytes.extend_from_slice(&(#param_ident as f32).to_bits().to_le_bytes());
224 },
225 Self::Double => quote! {
226 _stdin_bytes.extend_from_slice(&(#param_ident as f64).to_bits().to_le_bytes());
227 },
228 Self::Char => quote! {
229 {
230 let _c = #param_ident as u32;
231 assert!(_c <= 0xFFFF, "inline_csharp: char value exceeds u16 range");
232 _stdin_bytes.extend_from_slice(&(_c as u16).to_le_bytes());
233 }
234 },
235 Self::Str => quote! {
236 {
237 let _b = #param_ident.as_bytes();
238 let _len = _b.len() as u32;
239 _stdin_bytes.extend_from_slice(&_len.to_le_bytes());
240 _stdin_bytes.extend_from_slice(_b);
241 }
242 },
243 Self::Array(inner) | Self::List(inner) => {
244 let item_var = format_ident!("_item{}", depth);
245 let item_expr = quote! { #item_var };
246 let inner_ser = inner.rust_ser_ts(&item_expr, depth + 1);
247 quote! {
248 {
249 _stdin_bytes.extend_from_slice(&(#param_ident.len() as u32).to_le_bytes());
250 for &#item_var in #param_ident {
251 #inner_ser
252 }
253 }
254 }
255 }
256 Self::Nullable(inner) => {
257 let inner_var = format_ident!("_inner{}", depth);
258 let inner_expr = quote! { #inner_var };
259 let inner_ser = inner.rust_ser_ts(&inner_expr, depth + 1);
260 quote! {
261 match #param_ident {
262 ::std::option::Option::None => _stdin_bytes.push(0u8),
263 ::std::option::Option::Some(#inner_var) => {
264 _stdin_bytes.push(1u8);
265 #inner_ser
266 }
267 }
268 }
269 }
270 }
271 }
272
273 fn rust_deser(&self) -> proc_macro2::TokenStream {
276 match self {
277 Self::Str => {
278 quote! { ::std::string::String::from_utf8(_raw)? }
280 }
281 Self::Sbyte => quote! { i8::from_le_bytes([_raw[0]]) },
282 Self::Byte => quote! { _raw[0] },
283 Self::Short => quote! { i16::from_le_bytes([_raw[0], _raw[1]]) },
284 Self::Ushort => quote! { u16::from_le_bytes([_raw[0], _raw[1]]) },
285 Self::Int => quote! { i32::from_le_bytes([_raw[0], _raw[1], _raw[2], _raw[3]]) },
286 Self::Uint => quote! { u32::from_le_bytes([_raw[0], _raw[1], _raw[2], _raw[3]]) },
287 Self::Long => {
288 quote! {
289 i64::from_le_bytes([
290 _raw[0], _raw[1], _raw[2], _raw[3],
291 _raw[4], _raw[5], _raw[6], _raw[7],
292 ])
293 }
294 }
295 Self::Ulong => {
296 quote! {
297 u64::from_le_bytes([
298 _raw[0], _raw[1], _raw[2], _raw[3],
299 _raw[4], _raw[5], _raw[6], _raw[7],
300 ])
301 }
302 }
303 Self::Float => {
304 quote! { f32::from_bits(u32::from_le_bytes([_raw[0], _raw[1], _raw[2], _raw[3]])) }
305 }
306 Self::Double => {
307 quote! {
308 f64::from_bits(u64::from_le_bytes([
309 _raw[0], _raw[1], _raw[2], _raw[3],
310 _raw[4], _raw[5], _raw[6], _raw[7],
311 ]))
312 }
313 }
314 Self::Bool => quote! { _raw[0] != 0 },
315 Self::Char => {
316 quote! {
317 ::std::char::from_u32(u16::from_le_bytes([_raw[0], _raw[1]]) as u32)
318 .ok_or(::inline_csharp::CsharpError::InvalidChar)?
319 }
320 }
321 _ => {
322 let rust_type = self.rust_return_type_ts();
324 let read_expr = rust_read_element(self, 0);
325 quote! {
326 {
327 let mut _cur = 0usize;
328 let _result: #rust_type = #read_expr;
329 _result
330 }
331 }
332 }
333 }
334 }
335
336 fn ct_csharp_tokens(&self, bytes: Vec<u8>) -> Result<proc_macro2::TokenStream, String> {
339 match self {
340 Self::Str => {
341 let s = String::from_utf8(bytes)
343 .map_err(|_| "ct_csharp: C# string is not valid UTF-8".to_string())?;
344 let lit = format!("{s:?}");
345 proc_macro2::TokenStream::from_str(&lit)
346 .map_err(|e| format!("ct_csharp: produced invalid Rust token: {e}"))
347 }
348 Self::Sbyte
349 | Self::Byte
350 | Self::Short
351 | Self::Ushort
352 | Self::Int
353 | Self::Uint
354 | Self::Long
355 | Self::Ulong
356 | Self::Float
357 | Self::Double
358 | Self::Bool
359 | Self::Char => {
360 let (lit, _) = scalar_ct_lit(self, &bytes, 0)?;
361 proc_macro2::TokenStream::from_str(&lit)
362 .map_err(|e| format!("ct_csharp: produced invalid Rust token: {e}"))
363 }
364 _ => {
365 let mut cur = 0usize;
366 let ts = ct_csharp_tokens_recursive(self, &bytes, &mut cur)?;
367 Ok(ts)
368 }
369 }
370 }
371
372 fn csharp_main(&self, params: &[(CsharpType, String)]) -> String {
377 let param_reads = if params.is_empty() {
378 String::new()
379 } else {
380 let mut s = String::from(
381 "\t\tSystem.IO.BinaryReader _br = new System.IO.BinaryReader(System.Console.OpenStandardInput());\n",
382 );
383 for (ty, name) in params {
384 writeln!(s, "\t\t{}", csharp_br_read(ty, name, 0)).unwrap();
385 }
386 s
387 };
388
389 let run_args: String = params
390 .iter()
391 .map(|(_, name)| name.as_str())
392 .collect::<Vec<_>>()
393 .join(", ");
394
395 let result_ty = self.csharp_type_name();
396 let serialize = csharp_bw_write(self, "_result", 0);
397
398 format!(
399 "\tstatic void Main() {{\n\
400 {param_reads}\t\t{result_ty} _result = Run({run_args});\n\
401 \t\t{serialize}\n\
402 \t}}"
403 )
404 }
405}
406
407fn csharp_br_read(ty: &CsharpType, name: &str, depth: usize) -> String {
411 match ty {
412 CsharpType::Sbyte => format!("sbyte {name} = _br.ReadSByte();"),
413 CsharpType::Byte => format!("byte {name} = _br.ReadByte();"),
414 CsharpType::Short => format!("short {name} = _br.ReadInt16();"),
415 CsharpType::Ushort => format!("ushort {name} = _br.ReadUInt16();"),
416 CsharpType::Int => format!("int {name} = _br.ReadInt32();"),
417 CsharpType::Uint => format!("uint {name} = _br.ReadUInt32();"),
418 CsharpType::Long => format!("long {name} = _br.ReadInt64();"),
419 CsharpType::Ulong => format!("ulong {name} = _br.ReadUInt64();"),
420 CsharpType::Float => format!("float {name} = _br.ReadSingle();"),
421 CsharpType::Double => format!("double {name} = _br.ReadDouble();"),
422 CsharpType::Bool => format!("bool {name} = _br.ReadBoolean();"),
423 CsharpType::Char => format!("char {name} = (char)_br.ReadUInt16();"),
424 CsharpType::Str => {
425 format!(
426 "uint _len_{name} = _br.ReadUInt32();\n\
427 \t\tbyte[] _b_{name} = _br.ReadBytes((int)_len_{name});\n\
428 \t\tstring {name} = System.Text.Encoding.UTF8.GetString(_b_{name});"
429 )
430 }
431 CsharpType::Array(inner) => {
432 let count_var = format!("_count_{name}_{depth}");
433 let i_var = format!("_i_{name}_{depth}");
434 let elem_var = format!("_elem_{name}_{depth}");
435 let inner_cs_type = inner.csharp_type_name();
436 let inner_read = csharp_br_read(inner, &elem_var, depth + 1);
437 let mut base = inner_cs_type.as_str();
441 let mut trailing = "";
442 if base.ends_with("[]") {
443 let end = base.len()
444 - base
445 .chars()
446 .rev()
447 .take_while(|&c| c == '[' || c == ']')
448 .count();
449 trailing = &base[end..];
450 base = &base[..end];
451 }
452 format!(
453 "uint {count_var} = _br.ReadUInt32();\n\
454 \t\t{inner_cs_type}[] {name} = new {base}[{count_var}]{trailing};\n\
455 \t\tfor (int {i_var} = 0; {i_var} < {count_var}; {i_var}++) {{\n\
456 \t\t\t{inner_read}\n\
457 \t\t\t{name}[{i_var}] = {elem_var};\n\
458 \t\t}}"
459 )
460 }
461 CsharpType::List(inner) => {
462 let count_var = format!("_count_{name}_{depth}");
463 let i_var = format!("_i_{name}_{depth}");
464 let elem_var = format!("_elem_{name}_{depth}");
465 let inner_cs_type = inner.csharp_type_name();
466 let inner_read = csharp_br_read(inner, &elem_var, depth + 1);
467 format!(
468 "uint {count_var} = _br.ReadUInt32();\n\
469 \t\tSystem.Collections.Generic.List<{inner_cs_type}> {name} = new();\n\
470 \t\tfor (int {i_var} = 0; {i_var} < {count_var}; {i_var}++) {{\n\
471 \t\t\t{inner_read}\n\
472 \t\t\t{name}.Add({elem_var});\n\
473 \t\t}}"
474 )
475 }
476 CsharpType::Nullable(inner) => {
477 let tag_var = format!("_tag_{name}_{depth}");
478 let inner_var = format!("_inner_{name}_{depth}");
479 let inner_cs_type = inner.csharp_type_name();
480 let inner_read = csharp_br_read(inner, &inner_var, depth + 1);
481 format!(
482 "byte {tag_var} = _br.ReadByte();\n\
483 \t\t{inner_cs_type}? {name};\n\
484 \t\tif ({tag_var} != 0) {{\n\
485 \t\t\t{inner_read}\n\
486 \t\t\t{name} = {inner_var};\n\
487 \t\t}} else {{\n\
488 \t\t\t{name} = null;\n\
489 \t\t}}"
490 )
491 }
492 }
493}
494
495fn csharp_bw_write(ty: &CsharpType, var: &str, _depth: usize) -> String {
500 match ty {
501 CsharpType::Str => {
502 format!(
504 "byte[] _b = System.Text.Encoding.UTF8.GetBytes({var}); \
505 System.Console.OpenStandardOutput().Write(_b, 0, _b.Length);"
506 )
507 }
508 CsharpType::Char => {
509 format!(
510 "System.IO.BinaryWriter _bw = new System.IO.BinaryWriter(System.Console.OpenStandardOutput());\n\
511 \t\t_bw.Write((ushort){var});\n\
512 \t\t_bw.Flush();"
513 )
514 }
515 CsharpType::Array(inner) => {
516 let inner_cs_type = inner.csharp_type_name();
517 let ser_body = csharp_ser_element(inner, "_e0", "_bw", 1);
518 format!(
519 "System.IO.BinaryWriter _bw = new System.IO.BinaryWriter(System.Console.OpenStandardOutput());\n\
520 \t\t_bw.Write((uint){var}.Length);\n\
521 \t\tforeach ({inner_cs_type} _e0 in {var}) {{\n\
522 \t\t\t{ser_body}\n\
523 \t\t}}\n\
524 \t\t_bw.Flush();"
525 )
526 }
527 CsharpType::List(inner) => {
528 let inner_cs_type = inner.csharp_type_name();
529 let ser_body = csharp_ser_element(inner, "_e0", "_bw", 1);
530 format!(
531 "System.IO.BinaryWriter _bw = new System.IO.BinaryWriter(System.Console.OpenStandardOutput());\n\
532 \t\t_bw.Write((uint){var}.Count);\n\
533 \t\tforeach ({inner_cs_type} _e0 in {var}) {{\n\
534 \t\t\t{ser_body}\n\
535 \t\t}}\n\
536 \t\t_bw.Flush();"
537 )
538 }
539 CsharpType::Nullable(inner) => {
540 let inner_cs_type = inner.csharp_type_name();
541 if inner.is_value_type() {
542 let ser_body = csharp_ser_element(inner, &format!("{var}.Value"), "_bw", 1);
544 format!(
545 "System.IO.BinaryWriter _bw = new System.IO.BinaryWriter(System.Console.OpenStandardOutput());\n\
546 \t\tif ({var}.HasValue) {{\n\
547 \t\t\t_bw.Write((byte)1);\n\
548 \t\t\t{inner_cs_type} _opt_val = {var}.Value;\n\
549 \t\t\t{ser_body}\n\
550 \t\t}} else {{\n\
551 \t\t\t_bw.Write((byte)0);\n\
552 \t\t}}\n\
553 \t\t_bw.Flush();"
554 )
555 } else {
556 let ser_body = csharp_ser_element(inner, "_opt_val", "_bw", 1);
558 format!(
559 "System.IO.BinaryWriter _bw = new System.IO.BinaryWriter(System.Console.OpenStandardOutput());\n\
560 \t\tif ({var} != null) {{\n\
561 \t\t\t_bw.Write((byte)1);\n\
562 \t\t\t{inner_cs_type} _opt_val = {var};\n\
563 \t\t\t{ser_body}\n\
564 \t\t}} else {{\n\
565 \t\t\t_bw.Write((byte)0);\n\
566 \t\t}}\n\
567 \t\t_bw.Flush();"
568 )
569 }
570 }
571 _ => {
573 let cs_type = ty.csharp_type_name();
574 format!(
575 "System.IO.BinaryWriter _bw = new System.IO.BinaryWriter(System.Console.OpenStandardOutput());\n\
576 \t\t_bw.Write(({cs_type}){var});\n\
577 \t\t_bw.Flush();"
578 )
579 }
580 }
581}
582
583fn csharp_ser_element(ty: &CsharpType, var: &str, bw_name: &str, depth: usize) -> String {
586 match ty {
587 CsharpType::Char => {
588 format!("{bw_name}.Write((ushort){var});")
589 }
590 CsharpType::Str => {
591 format!(
592 "{{ byte[] _b{depth} = System.Text.Encoding.UTF8.GetBytes({var}); \
593 {bw_name}.Write((uint)_b{depth}.Length); \
594 {bw_name}.Write(_b{depth}); }}"
595 )
596 }
597 CsharpType::Array(inner) => {
598 let inner_cs_type = inner.csharp_type_name();
599 let elem_var = format!("_e{depth}");
600 let inner_ser = csharp_ser_element(inner, &elem_var, bw_name, depth + 1);
601 format!(
602 "{bw_name}.Write((uint)({var}).Length);\n\
603 \t\t\tforeach ({inner_cs_type} {elem_var} in ({var})) {{\n\
604 \t\t\t\t{inner_ser}\n\
605 \t\t\t}}"
606 )
607 }
608 CsharpType::List(inner) => {
609 let inner_cs_type = inner.csharp_type_name();
610 let elem_var = format!("_e{depth}");
611 let inner_ser = csharp_ser_element(inner, &elem_var, bw_name, depth + 1);
612 format!(
613 "{bw_name}.Write((uint)({var}).Count);\n\
614 \t\t\tforeach ({inner_cs_type} {elem_var} in ({var})) {{\n\
615 \t\t\t\t{inner_ser}\n\
616 \t\t\t}}"
617 )
618 }
619 CsharpType::Nullable(inner) => {
620 let inner_cs_type = inner.csharp_type_name();
621 let opt_inner_var = format!("_opt_inner{depth}");
622 let inner_ser = csharp_ser_element(inner, &opt_inner_var, bw_name, depth + 1);
623 if inner.is_value_type() {
624 format!(
625 "if (({var}).HasValue) {{\n\
626 \t\t\t\t{bw_name}.Write((byte)1);\n\
627 \t\t\t\t{inner_cs_type} {opt_inner_var} = ({var}).Value;\n\
628 \t\t\t\t{inner_ser}\n\
629 \t\t\t}} else {{\n\
630 \t\t\t\t{bw_name}.Write((byte)0);\n\
631 \t\t\t}}"
632 )
633 } else {
634 format!(
635 "if (({var}) != null) {{\n\
636 \t\t\t\t{bw_name}.Write((byte)1);\n\
637 \t\t\t\t{inner_cs_type} {opt_inner_var} = {var};\n\
638 \t\t\t\t{inner_ser}\n\
639 \t\t\t}} else {{\n\
640 \t\t\t\t{bw_name}.Write((byte)0);\n\
641 \t\t\t}}"
642 )
643 }
644 }
645 _ => {
647 let cs_type = ty.csharp_type_name();
648 format!("{bw_name}.Write(({cs_type}){var});")
649 }
650 }
651}
652
653#[allow(clippy::too_many_lines)]
658fn rust_read_element(ty: &CsharpType, depth: usize) -> proc_macro2::TokenStream {
659 match ty {
660 CsharpType::Sbyte => quote! {{
661 let _val = i8::from_le_bytes([_raw[_cur]]);
662 _cur += 1;
663 _val
664 }},
665 CsharpType::Byte => quote! {{
666 let _val = _raw[_cur];
667 _cur += 1;
668 _val
669 }},
670 CsharpType::Short => quote! {{
671 let _val = i16::from_le_bytes([_raw[_cur], _raw[_cur + 1]]);
672 _cur += 2;
673 _val
674 }},
675 CsharpType::Ushort => quote! {{
676 let _val = u16::from_le_bytes([_raw[_cur], _raw[_cur + 1]]);
677 _cur += 2;
678 _val
679 }},
680 CsharpType::Int => quote! {{
681 let _val = i32::from_le_bytes([_raw[_cur], _raw[_cur + 1], _raw[_cur + 2], _raw[_cur + 3]]);
682 _cur += 4;
683 _val
684 }},
685 CsharpType::Uint => quote! {{
686 let _val = u32::from_le_bytes([_raw[_cur], _raw[_cur + 1], _raw[_cur + 2], _raw[_cur + 3]]);
687 _cur += 4;
688 _val
689 }},
690 CsharpType::Long => quote! {{
691 let _val = i64::from_le_bytes([
692 _raw[_cur], _raw[_cur + 1], _raw[_cur + 2], _raw[_cur + 3],
693 _raw[_cur + 4], _raw[_cur + 5], _raw[_cur + 6], _raw[_cur + 7],
694 ]);
695 _cur += 8;
696 _val
697 }},
698 CsharpType::Ulong => quote! {{
699 let _val = u64::from_le_bytes([
700 _raw[_cur], _raw[_cur + 1], _raw[_cur + 2], _raw[_cur + 3],
701 _raw[_cur + 4], _raw[_cur + 5], _raw[_cur + 6], _raw[_cur + 7],
702 ]);
703 _cur += 8;
704 _val
705 }},
706 CsharpType::Float => quote! {{
707 let _val = f32::from_bits(u32::from_le_bytes([_raw[_cur], _raw[_cur + 1], _raw[_cur + 2], _raw[_cur + 3]]));
708 _cur += 4;
709 _val
710 }},
711 CsharpType::Double => quote! {{
712 let _val = f64::from_bits(u64::from_le_bytes([
713 _raw[_cur], _raw[_cur + 1], _raw[_cur + 2], _raw[_cur + 3],
714 _raw[_cur + 4], _raw[_cur + 5], _raw[_cur + 6], _raw[_cur + 7],
715 ]));
716 _cur += 8;
717 _val
718 }},
719 CsharpType::Bool => quote! {{
720 let _val = _raw[_cur] != 0;
721 _cur += 1;
722 _val
723 }},
724 CsharpType::Char => quote! {{
725 let _val = ::std::char::from_u32(u16::from_le_bytes([_raw[_cur], _raw[_cur + 1]]) as u32)
726 .ok_or(::inline_csharp::CsharpError::InvalidChar)?;
727 _cur += 2;
728 _val
729 }},
730 CsharpType::Str => quote! {{
732 let _slen = u32::from_le_bytes([_raw[_cur], _raw[_cur + 1], _raw[_cur + 2], _raw[_cur + 3]]) as usize;
733 _cur += 4;
734 let _val = ::std::string::String::from_utf8(_raw[_cur.._cur + _slen].to_vec())?;
735 _cur += _slen;
736 _val
737 }},
738 CsharpType::Array(inner) | CsharpType::List(inner) => {
739 let n_var = format_ident!("_n{}", depth);
740 let v_var = format_ident!("_v{}", depth);
741 let inner_rust_type = inner.rust_return_type_ts();
742 let inner_read = rust_read_element(inner, depth + 1);
743 quote! {{
744 let #n_var = u32::from_le_bytes([_raw[_cur], _raw[_cur + 1], _raw[_cur + 2], _raw[_cur + 3]]) as usize;
745 _cur += 4;
746 let mut #v_var: ::std::vec::Vec<#inner_rust_type> = ::std::vec::Vec::with_capacity(#n_var);
747 for _ in 0..#n_var {
748 let _item = #inner_read;
749 #v_var.push(_item);
750 }
751 #v_var
752 }}
753 }
754 CsharpType::Nullable(inner) => {
755 let inner_rust_type = inner.rust_return_type_ts();
756 let inner_read = rust_read_element(inner, depth + 1);
757 quote! {{
758 let _tag = _raw[_cur];
759 _cur += 1;
760 if _tag == 0 {
761 ::std::option::Option::None::<#inner_rust_type>
762 } else {
763 ::std::option::Option::Some(#inner_read)
764 }
765 }}
766 }
767 }
768}
769
770#[allow(clippy::too_many_lines)]
775fn scalar_ct_lit(ty: &CsharpType, bytes: &[u8], offset: usize) -> Result<(String, usize), String> {
776 let b = &bytes[offset..];
777 match ty {
778 CsharpType::Sbyte => {
779 if b.is_empty() {
780 return Err("ct_csharp: truncated sbyte element".to_string());
781 }
782 Ok((format!("{}", i8::from_le_bytes([b[0]])), 1))
783 }
784 CsharpType::Byte => {
785 if b.is_empty() {
786 return Err("ct_csharp: truncated byte element".to_string());
787 }
788 Ok((format!("{}", b[0]), 1))
789 }
790 CsharpType::Short => {
791 if b.len() < 2 {
792 return Err("ct_csharp: truncated short element".to_string());
793 }
794 Ok((format!("{}", i16::from_le_bytes([b[0], b[1]])), 2))
795 }
796 CsharpType::Ushort => {
797 if b.len() < 2 {
798 return Err("ct_csharp: truncated ushort element".to_string());
799 }
800 Ok((format!("{}", u16::from_le_bytes([b[0], b[1]])), 2))
801 }
802 CsharpType::Int => {
803 let arr: [u8; 4] = b[..4]
804 .try_into()
805 .map_err(|_| "ct_csharp: truncated int element")?;
806 Ok((format!("{}", i32::from_le_bytes(arr)), 4))
807 }
808 CsharpType::Uint => {
809 let arr: [u8; 4] = b[..4]
810 .try_into()
811 .map_err(|_| "ct_csharp: truncated uint element")?;
812 Ok((format!("{}", u32::from_le_bytes(arr)), 4))
813 }
814 CsharpType::Long => {
815 let arr: [u8; 8] = b[..8]
816 .try_into()
817 .map_err(|_| "ct_csharp: truncated long element")?;
818 Ok((format!("{}", i64::from_le_bytes(arr)), 8))
819 }
820 CsharpType::Ulong => {
821 let arr: [u8; 8] = b[..8]
822 .try_into()
823 .map_err(|_| "ct_csharp: truncated ulong element")?;
824 Ok((format!("{}", u64::from_le_bytes(arr)), 8))
825 }
826 CsharpType::Float => {
827 let arr: [u8; 4] = b[..4]
828 .try_into()
829 .map_err(|_| "ct_csharp: truncated float element")?;
830 let bits = u32::from_le_bytes(arr);
831 Ok((format!("f32::from_bits(0x{bits:08x}_u32)"), 4))
832 }
833 CsharpType::Double => {
834 let arr: [u8; 8] = b[..8]
835 .try_into()
836 .map_err(|_| "ct_csharp: truncated double element")?;
837 let bits = u64::from_le_bytes(arr);
838 Ok((format!("f64::from_bits(0x{bits:016x}_u64)"), 8))
839 }
840 CsharpType::Bool => {
841 if b.is_empty() {
842 return Err("ct_csharp: truncated bool element".to_string());
843 }
844 Ok((
845 if b[0] != 0 {
846 "true".to_string()
847 } else {
848 "false".to_string()
849 },
850 1,
851 ))
852 }
853 CsharpType::Char => {
854 if b.len() < 2 {
855 return Err("ct_csharp: truncated char element".to_string());
856 }
857 let code_unit = u16::from_le_bytes([b[0], b[1]]);
858 let c = char::from_u32(u32::from(code_unit))
859 .ok_or("ct_csharp: C# char is not a valid Unicode scalar value")?;
860 Ok((format!("{c:?}"), 2))
861 }
862 CsharpType::Str => {
863 if b.len() < 4 {
865 return Err("ct_csharp: truncated String length prefix".to_string());
866 }
867 let len = u32::from_le_bytes(b[..4].try_into().unwrap()) as usize;
868 if b.len() < 4 + len {
869 return Err(format!(
870 "ct_csharp: truncated String element (expected {len} bytes)"
871 ));
872 }
873 let s = String::from_utf8(b[4..4 + len].to_vec())
874 .map_err(|_| "ct_csharp: String element is not valid UTF-8".to_string())?;
875 Ok((format!("{s:?}"), 4 + len))
876 }
877 _ => Err("ct_csharp: scalar_ct_lit called on non-scalar type".to_string()),
878 }
879}
880
881fn ct_csharp_tokens_recursive(
884 ty: &CsharpType,
885 bytes: &[u8],
886 cur: &mut usize,
887) -> Result<proc_macro2::TokenStream, String> {
888 match ty {
889 CsharpType::Array(inner) | CsharpType::List(inner) => {
890 if bytes[*cur..].len() < 4 {
891 return Err("ct_csharp: array/list output too short (missing length)".to_string());
892 }
893 let n = u32::from_le_bytes(bytes[*cur..*cur + 4].try_into().unwrap()) as usize;
894 *cur += 4;
895 let mut lits: Vec<proc_macro2::TokenStream> = Vec::with_capacity(n);
896 for _ in 0..n {
897 lits.push(ct_csharp_tokens_recursive(inner, bytes, cur)?);
898 }
899 Ok(quote! { [#(#lits),*] })
900 }
901 CsharpType::Nullable(inner) => {
902 if bytes[*cur..].is_empty() {
903 return Err("ct_csharp: nullable output is empty".to_string());
904 }
905 let tag = bytes[*cur];
906 *cur += 1;
907 if tag == 0 {
908 proc_macro2::TokenStream::from_str("::std::option::Option::None")
909 .map_err(|e| format!("ct_csharp: produced invalid Rust token: {e}"))
910 } else {
911 let inner_ts = ct_csharp_tokens_recursive(inner, bytes, cur)?;
912 Ok(quote! { ::std::option::Option::Some(#inner_ts) })
913 }
914 }
915 CsharpType::Str => {
916 let (lit, consumed) = scalar_ct_lit(ty, bytes, *cur)?;
918 *cur += consumed;
919 proc_macro2::TokenStream::from_str(&lit)
920 .map_err(|e| format!("ct_csharp: produced invalid Rust token: {e}"))
921 }
922 _ => {
923 let (lit, consumed) = scalar_ct_lit(ty, bytes, *cur)?;
925 *cur += consumed;
926 proc_macro2::TokenStream::from_str(&lit)
927 .map_err(|e| format!("ct_csharp: produced invalid Rust token: {e}"))
928 }
929 }
930}
931
932struct ParsedCsharp {
936 usings: String,
938 namespace_decl: String,
940 outer: String,
942 body: String,
944 params: Vec<(CsharpType, String)>,
946 csharp_type: CsharpType,
948}
949
950fn parse_csharp_type(tts: &[TokenTree]) -> Result<(CsharpType, usize), String> {
959 if tts.is_empty() {
960 return Err("inline_csharp: unexpected end of tokens while parsing C# type".to_string());
961 }
962
963 match tts.first() {
964 Some(TokenTree::Ident(id)) => {
965 let name = id.to_string();
966 let (base_ty, consumed) = if name == "List" {
967 if !matches!(tts.get(1), Some(TokenTree::Punct(p)) if p.as_char() == '<') {
969 return Err("inline_csharp: expected `<` after `List`".to_string());
970 }
971 let (inner_ty, inner_consumed) = parse_csharp_type_inner(&tts[2..])?;
972 let close_idx = 2 + inner_consumed;
973 if !matches!(tts.get(close_idx), Some(TokenTree::Punct(p)) if p.as_char() == '>') {
974 return Err("inline_csharp: expected `>` to close `List<...>`".to_string());
975 }
976 (CsharpType::List(Box::new(inner_ty)), close_idx + 1)
977 } else if let Some(scalar) = CsharpType::from_name(&name) {
978 (scalar, 1)
979 } else {
980 return Err(format!(
981 "inline_csharp: `{name}` is not a supported C# type; \
982 scalar types: sbyte byte short ushort int uint long ulong float double bool char string"
983 ));
984 };
985
986 let mut ty = base_ty;
988 let mut total_consumed = consumed;
989 while matches!(
990 tts.get(total_consumed),
991 Some(TokenTree::Group(g))
992 if g.delimiter() == proc_macro2::Delimiter::Bracket
993 && g.stream().is_empty()
994 ) {
995 ty = CsharpType::Array(Box::new(ty));
996 total_consumed += 1;
997 }
998
999 if matches!(tts.get(total_consumed), Some(TokenTree::Punct(p)) if p.as_char() == '?') {
1001 ty = CsharpType::Nullable(Box::new(ty));
1002 total_consumed += 1;
1003 }
1004
1005 Ok((ty, total_consumed))
1006 }
1007 _ => Err("inline_csharp: expected a C# type name".to_string()),
1008 }
1009}
1010
1011fn parse_csharp_type_inner(tts: &[TokenTree]) -> Result<(CsharpType, usize), String> {
1013 if tts.is_empty() {
1014 return Err(
1015 "inline_csharp: unexpected end of tokens while parsing C# type argument".to_string(),
1016 );
1017 }
1018
1019 match tts.first() {
1020 Some(TokenTree::Ident(id)) => {
1021 let name = id.to_string();
1022 let (base_ty, consumed) = if name == "List" {
1023 if !matches!(tts.get(1), Some(TokenTree::Punct(p)) if p.as_char() == '<') {
1024 return Err("inline_csharp: expected `<` after `List`".to_string());
1025 }
1026 let (inner_ty, inner_consumed) = parse_csharp_type_inner(&tts[2..])?;
1027 let close_idx = 2 + inner_consumed;
1028 if !matches!(tts.get(close_idx), Some(TokenTree::Punct(p)) if p.as_char() == '>') {
1029 return Err("inline_csharp: expected `>` to close `List<...>`".to_string());
1030 }
1031 (CsharpType::List(Box::new(inner_ty)), close_idx + 1)
1032 } else if let Some(scalar) = CsharpType::from_name(&name) {
1033 (scalar, 1)
1034 } else {
1035 return Err(format!(
1036 "inline_csharp: `{name}` is not a supported C# type argument; \
1037 supported: sbyte byte short ushort int uint long ulong float double bool char string"
1038 ));
1039 };
1040
1041 let mut ty = base_ty;
1043 let mut total_consumed = consumed;
1044 while matches!(
1045 tts.get(total_consumed),
1046 Some(TokenTree::Group(g))
1047 if g.delimiter() == proc_macro2::Delimiter::Bracket
1048 && g.stream().is_empty()
1049 ) {
1050 ty = CsharpType::Array(Box::new(ty));
1051 total_consumed += 1;
1052 }
1053
1054 if matches!(tts.get(total_consumed), Some(TokenTree::Punct(p)) if p.as_char() == '?') {
1056 ty = CsharpType::Nullable(Box::new(ty));
1057 total_consumed += 1;
1058 }
1059
1060 Ok((ty, total_consumed))
1061 }
1062 _ => Err("inline_csharp: expected a C# type name inside `<>`".to_string()),
1063 }
1064}
1065
1066fn parse_run_return_type(tts: &[TokenTree]) -> Result<(CsharpType, usize, usize), String> {
1072 for i in 0..tts.len().saturating_sub(2) {
1073 if !matches!(&tts[i], TokenTree::Ident(id) if id == "static") {
1074 continue;
1075 }
1076
1077 let start = if i > 0
1079 && matches!(&tts[i - 1], TokenTree::Ident(id)
1080 if matches!(id.to_string().as_str(), "public" | "private" | "protected"))
1081 {
1082 i - 1
1083 } else {
1084 i
1085 };
1086
1087 let type_start = i + 1;
1088 if type_start >= tts.len() {
1089 continue;
1090 }
1091
1092 if let Ok((csharp_type, consumed)) = parse_csharp_type(&tts[type_start..]) {
1093 let run_idx = type_start + consumed;
1094 if matches!(tts.get(run_idx), Some(TokenTree::Ident(id)) if id == "Run") {
1095 return Ok((csharp_type, start, run_idx));
1096 }
1097 }
1098 }
1099 Err("inline_csharp: could not find `static <type> Run()` in C# body".to_string())
1100}
1101
1102fn parse_run_params(tts: &[TokenTree]) -> Result<Vec<(CsharpType, String)>, String> {
1105 let group = match tts.first() {
1106 Some(TokenTree::Group(g)) if g.delimiter() == proc_macro2::Delimiter::Parenthesis => g,
1107 _ => return Ok(vec![]),
1108 };
1109
1110 let inner: Vec<TokenTree> = group.stream().into_iter().collect();
1111 if inner.is_empty() {
1112 return Ok(vec![]);
1113 }
1114
1115 let mut params = Vec::new();
1116 let mut segments: Vec<Vec<TokenTree>> = Vec::new();
1117 let mut current: Vec<TokenTree> = Vec::new();
1118 let mut angle_depth = 0i32;
1119 for tt in inner {
1120 if matches!(&tt, TokenTree::Punct(p) if p.as_char() == '<') {
1121 angle_depth += 1;
1122 current.push(tt);
1123 } else if matches!(&tt, TokenTree::Punct(p) if p.as_char() == '>') {
1124 angle_depth -= 1;
1125 current.push(tt);
1126 } else if matches!(&tt, TokenTree::Punct(p) if p.as_char() == ',') && angle_depth == 0 {
1127 segments.push(std::mem::take(&mut current));
1128 } else {
1129 current.push(tt);
1130 }
1131 }
1132 if !current.is_empty() {
1133 segments.push(current);
1134 }
1135
1136 for seg in segments {
1137 if seg.is_empty() {
1138 continue;
1139 }
1140
1141 let param_name = match seg.last() {
1142 Some(TokenTree::Ident(id)) => id.to_string(),
1143 _ => {
1144 return Err(
1145 "inline_csharp: unexpected token in Run() parameter list: expected a parameter name"
1146 .to_string(),
1147 );
1148 }
1149 };
1150
1151 let type_tts = &seg[..seg.len() - 1];
1152 if type_tts.is_empty() {
1153 return Err(format!(
1154 "inline_csharp: missing type for parameter `{param_name}`"
1155 ));
1156 }
1157
1158 let (csharp_type, consumed) = parse_csharp_type(type_tts).map_err(|e| {
1159 format!("inline_csharp: error parsing type of parameter `{param_name}`: {e}")
1160 })?;
1161
1162 if consumed != type_tts.len() {
1163 return Err(format!(
1164 "inline_csharp: unexpected tokens after type of parameter `{param_name}`"
1165 ));
1166 }
1167
1168 params.push((csharp_type, param_name));
1169 }
1170
1171 Ok(params)
1172}
1173
1174fn parse_csharp_source(stream: proc_macro2::TokenStream) -> Result<ParsedCsharp, String> {
1177 let tts: Vec<TokenTree> = stream.into_iter().collect();
1178
1179 let mut first_using_idx: Option<usize> = None;
1182 let mut last_using_end_idx: Option<usize> = None;
1183 let mut first_body_idx: Option<usize> = None;
1184 let mut in_usings = true;
1185 let mut i = 0usize;
1186
1187 while i < tts.len() && in_usings {
1188 match &tts[i] {
1189 TokenTree::Ident(id) if id == "using" => {
1190 let is_namespace_using = !matches!(tts.get(i + 1), Some(TokenTree::Ident(next))
1192 if next == "static" || next == "var");
1193 if is_namespace_using {
1194 first_using_idx.get_or_insert(i);
1195 let semi = tts[i + 1..]
1197 .iter()
1198 .position(|t| matches!(t, TokenTree::Punct(p) if p.as_char() == ';'))
1199 .map(|rel| i + 1 + rel);
1200 if let Some(semi_idx) = semi {
1201 last_using_end_idx = Some(semi_idx);
1202 i = semi_idx + 1;
1203 } else {
1204 in_usings = false;
1205 first_body_idx = Some(i);
1206 }
1207 } else {
1208 in_usings = false;
1209 first_body_idx = Some(i);
1210 }
1211 }
1212 _ => {
1213 in_usings = false;
1214 first_body_idx = Some(i);
1215 }
1216 }
1217 }
1218 if first_body_idx.is_none() && i < tts.len() {
1219 first_body_idx = Some(i);
1220 }
1221 let body_start = first_body_idx.unwrap_or(tts.len());
1222
1223 let (csharp_type, run_rel_idx, run_rel_run_idx) = parse_run_return_type(&tts[body_start..])?;
1225 let run_abs_idx = body_start + run_rel_idx;
1226 let run_token_abs_idx = body_start + run_rel_run_idx;
1227
1228 let params = parse_run_params(&tts[run_token_abs_idx + 1..])?;
1230
1231 let slice_text = |lo: usize, hi: usize| -> String {
1233 if lo >= hi {
1234 return String::new();
1235 }
1236 tts[lo]
1237 .span()
1238 .join(tts[hi - 1].span())
1239 .and_then(|s| s.source_text())
1240 .unwrap_or_else(|| {
1241 tts[lo..hi]
1242 .iter()
1243 .map(std::string::ToString::to_string)
1244 .collect::<Vec<_>>()
1245 .join(" ")
1246 })
1247 };
1248
1249 let usings = match (first_using_idx, last_using_end_idx) {
1251 (Some(fi), Some(le)) => slice_text(fi, le + 1),
1252 _ => String::new(),
1253 };
1254
1255 let outer = slice_text(body_start, run_abs_idx);
1257
1258 let body = if run_abs_idx < tts.len() {
1260 let start_span = tts[run_abs_idx].span();
1261 let end_span = tts.last().unwrap().span();
1262 match start_span.join(end_span).and_then(|s| s.source_text()) {
1263 Some(raw) => raw,
1264 None => tts[run_abs_idx..]
1265 .iter()
1266 .map(std::string::ToString::to_string)
1267 .collect::<Vec<_>>()
1268 .join(" "),
1269 }
1270 } else {
1271 String::new()
1272 };
1273
1274 let (namespace_decl, outer) = strip_namespace_decl(outer);
1281
1282 Ok(ParsedCsharp {
1283 usings,
1284 namespace_decl,
1285 outer,
1286 body,
1287 params,
1288 csharp_type,
1289 })
1290}
1291
1292#[derive(Default)]
1295struct DotnetOpts {
1296 build_args: String,
1297 run_args: String,
1298 references: Vec<String>,
1299}
1300
1301fn extract_opts(input: proc_macro2::TokenStream) -> (DotnetOpts, proc_macro2::TokenStream) {
1304 let mut tts: Vec<TokenTree> = input.into_iter().collect();
1305 let mut opts = DotnetOpts::default();
1306 let mut cursor = 0;
1307
1308 loop {
1309 match try_parse_opt(&tts[cursor..]) {
1310 None => break,
1311 Some((key, val, consumed)) => {
1312 match key.as_str() {
1313 "build" => opts.build_args = val,
1314 "run" => opts.run_args = val,
1315 "reference" => opts.references.push(val),
1316 _ => break,
1317 }
1318 cursor += consumed;
1319 if let Some(TokenTree::Punct(p)) = tts.get(cursor)
1320 && p.as_char() == ','
1321 {
1322 cursor += 1;
1323 }
1324 }
1325 }
1326 }
1327
1328 let rest = tts.drain(cursor..).collect();
1329 (opts, rest)
1330}
1331
1332fn try_parse_opt(tts: &[TokenTree]) -> Option<(String, String, usize)> {
1335 let key = match tts.first() {
1336 Some(TokenTree::Ident(id)) => id.to_string(),
1337 _ => return None,
1338 };
1339 let Some(TokenTree::Punct(eq)) = tts.get(1) else {
1340 return None;
1341 };
1342 if eq.as_char() != '=' {
1343 return None;
1344 }
1345 let Some(TokenTree::Literal(lit)) = tts.get(2) else {
1346 return None;
1347 };
1348 let value = litrs::StringLit::try_from(lit).ok()?.value().to_owned();
1349 Some((key, value, 3))
1350}
1351
1352fn make_class_name(
1356 prefix: &str,
1357 usings: &str,
1358 outer: &str,
1359 body: &str,
1360 opts: &DotnetOpts,
1361) -> String {
1362 let mut h = DefaultHasher::new();
1363 usings.hash(&mut h);
1364 outer.hash(&mut h);
1365 body.hash(&mut h);
1366 opts.build_args.hash(&mut h);
1367 opts.run_args.hash(&mut h);
1368 opts.references.hash(&mut h);
1369 format!("{prefix}_{:016x}", h.finish())
1370}
1371
1372fn strip_namespace_decl(outer: String) -> (String, String) {
1380 let marker = "namespace ";
1381 let Some(i) = outer.find(marker) else {
1382 return (String::new(), outer);
1383 };
1384 if i > 0 && !outer[..i].ends_with(|c: char| c.is_whitespace()) {
1385 return (String::new(), outer);
1386 }
1387 let after_marker = i + marker.len();
1388 let rest = outer[after_marker..].trim_start();
1389 let offset_trim = outer[after_marker..].len() - rest.len();
1390 let Some(semi_in_rest) = rest.find(';') else {
1391 return (String::new(), outer);
1392 };
1393 let ns = rest[..semi_in_rest]
1394 .trim()
1395 .replace(|c: char| c.is_whitespace(), "");
1396 if ns.is_empty() {
1397 return (String::new(), outer);
1398 }
1399 let semi_abs = after_marker + offset_trim + semi_in_rest;
1401 let stripped = format!("{}{}", &outer[..i], &outer[semi_abs + 1..]);
1402 (format!("namespace {ns};"), stripped.trim().to_string())
1403}
1404
1405fn format_csharp_source(
1407 usings: &str,
1408 namespace_decl: &str,
1409 class_name: &str,
1410 outer: &str,
1411 body: &str,
1412 main_method: &str,
1413) -> String {
1414 format!(
1415 "{usings}\n{namespace_decl}\nclass {class_name} {{\n\n{outer}\n\n{body}\n\n{main_method}\n}}\n"
1416 )
1417}
1418
1419fn compile_run_csharp_now(
1421 class_name: &str,
1422 csharp_source: &str,
1423 build_raw: Option<&str>,
1424 run_raw: Option<&str>,
1425 references: &[&str],
1426) -> Result<Vec<u8>, String> {
1427 inline_csharp_core::run_csharp(
1428 class_name,
1429 csharp_source,
1430 build_raw.unwrap_or(""),
1431 run_raw.unwrap_or(""),
1432 references,
1433 &[],
1434 )
1435 .map_err(|e| e.to_string())
1436}
1437
1438#[allow(clippy::similar_names)]
1443fn make_runner_fn(
1444 parsed: ParsedCsharp,
1445 opts: DotnetOpts,
1446 prefix: &str,
1447) -> proc_macro2::TokenStream {
1448 let ParsedCsharp {
1449 usings,
1450 namespace_decl,
1451 outer,
1452 body,
1453 params,
1454 csharp_type,
1455 } = parsed;
1456
1457 let class_name = make_class_name(prefix, &usings, &outer, &body, &opts);
1458 let main_method = csharp_type.csharp_main(¶ms);
1459 let csharp_source = format_csharp_source(
1460 &usings,
1461 &namespace_decl,
1462 &class_name,
1463 &outer,
1464 &body,
1465 &main_method,
1466 );
1467
1468 let build_raw = opts.build_args;
1469 let run_raw = opts.run_args;
1470 let reference_strs: Vec<proc_macro2::TokenStream> = opts
1471 .references
1472 .iter()
1473 .map(|r| {
1474 let lit = proc_macro2::Literal::string(r);
1475 quote! { #lit }
1476 })
1477 .collect();
1478
1479 let deser = csharp_type.rust_deser();
1480 let ret_ty = csharp_type.rust_return_type_ts();
1481
1482 let fn_params: Vec<proc_macro2::TokenStream> = params
1483 .iter()
1484 .map(|(ty, name)| {
1485 let ident = proc_macro2::Ident::new(name, proc_macro2::Span::call_site());
1486 let param_ty = ty.rust_param_type_ts();
1487 quote! { #ident: #param_ty }
1488 })
1489 .collect();
1490
1491 let ser_stmts: Vec<proc_macro2::TokenStream> = params
1492 .iter()
1493 .map(|(ty, name)| {
1494 let ident = proc_macro2::Ident::new(name, proc_macro2::Span::call_site());
1495 let ident_ts = quote! { #ident };
1496 ty.rust_ser_ts(&ident_ts, 0)
1497 })
1498 .collect();
1499
1500 quote! {
1501 fn __csharp_runner(#(#fn_params),*) -> ::std::result::Result<#ret_ty, ::inline_csharp::CsharpError> {
1502 let mut _stdin_bytes: ::std::vec::Vec<u8> = ::std::vec::Vec::new();
1503 #(#ser_stmts)*
1504 let _raw = ::inline_csharp::run_csharp(
1505 #class_name,
1506 #csharp_source,
1507 #build_raw,
1508 #run_raw,
1509 &[#(#reference_strs),*],
1510 &_stdin_bytes,
1511 )?;
1512 ::std::result::Result::Ok(#deser)
1513 }
1514 }
1515}
1516
1517fn ct_csharp_impl(input: proc_macro2::TokenStream) -> Result<proc_macro2::TokenStream, String> {
1520 let (opts, input) = extract_opts(input);
1521
1522 let ParsedCsharp {
1523 usings,
1524 namespace_decl,
1525 outer,
1526 body,
1527 csharp_type,
1528 ..
1529 } = parse_csharp_source(input)?;
1530
1531 let class_name = make_class_name("CtCsharp", &usings, &outer, &body, &opts);
1532 let main_method = csharp_type.csharp_main(&[]);
1533 let csharp_source = format_csharp_source(
1534 &usings,
1535 &namespace_decl,
1536 &class_name,
1537 &outer,
1538 &body,
1539 &main_method,
1540 );
1541
1542 let refs: Vec<&str> = opts.references.iter().map(String::as_str).collect();
1543 let bytes = compile_run_csharp_now(
1544 &class_name,
1545 &csharp_source,
1546 Some(&opts.build_args),
1547 Some(&opts.run_args),
1548 &refs,
1549 )?;
1550 csharp_type.ct_csharp_tokens(bytes)
1551}
1552
1553#[proc_macro]
1586#[allow(clippy::similar_names)]
1587pub fn csharp(input: TokenStream) -> TokenStream {
1588 let input2 = proc_macro2::TokenStream::from(input);
1589 let (opts, input2) = extract_opts(input2);
1590
1591 let parsed = match parse_csharp_source(input2) {
1592 Ok(p) => p,
1593 Err(msg) => return quote! { compile_error!(#msg) }.into(),
1594 };
1595
1596 let runner_fn = make_runner_fn(parsed, opts, "InlineCsharp");
1597
1598 let generated = quote! {
1599 {
1600 #runner_fn
1601 __csharp_runner()
1602 }
1603 };
1604
1605 generated.into()
1606}
1607
1608#[proc_macro]
1631#[allow(clippy::similar_names)]
1632pub fn csharp_fn(input: TokenStream) -> TokenStream {
1633 let input2 = proc_macro2::TokenStream::from(input);
1634 let (opts, input2) = extract_opts(input2);
1635
1636 let parsed = match parse_csharp_source(input2) {
1637 Ok(p) => p,
1638 Err(msg) => return quote! { compile_error!(#msg) }.into(),
1639 };
1640
1641 let runner_fn = make_runner_fn(parsed, opts, "InlineCsharp");
1642
1643 let generated = quote! {
1644 {
1645 #runner_fn
1646 __csharp_runner
1647 }
1648 };
1649
1650 generated.into()
1651}
1652
1653#[proc_macro]
1674pub fn ct_csharp(input: TokenStream) -> TokenStream {
1675 match ct_csharp_impl(proc_macro2::TokenStream::from(input)) {
1676 Ok(ts) => ts.into(),
1677 Err(msg) => quote! { compile_error!(#msg) }.into(),
1678 }
1679}