use hyperlight_component_util::emit::ResolvedBoundVar;
use hyperlight_component_util::etypes::{Defined, FloatWidth, Handleable, IntWidth, Tyvar, Value};
use crate::names::{Src, case, member, pascal};
use crate::{RT, S};
enum Target<'b> {
Named(usize),
Alias(Value<'b>, usize),
}
fn target<'b>(s: &S<'_, 'b>, v: &Value<'b>) -> Option<Target<'b>> {
let Value::Var(Some(Tyvar::Bound(n)), _) = v else {
return None;
};
let ResolvedBoundVar::Definite {
final_bound_var,
ty: Defined::Value(def),
} = s.resolve_bound_var(*n)
else {
unreachable!("rejected while collecting");
};
let var = s.var_offset + final_bound_var as usize;
Some(match s.root_mod.decl_index(var) {
Some(i) => Target::Named(i),
None => Target::Alias(def, var + 1),
})
}
fn alias<'b, R>(s: &mut S<'_, 'b>, offset: usize, f: impl FnOnce(&mut S<'_, 'b>) -> R) -> R {
let mut s = s.clone();
s.var_offset = offset;
f(&mut s)
}
fn resource(s: &S, h: &Handleable) -> usize {
let Handleable::Var(Tyvar::Bound(n)) = h else {
unreachable!("rejected while collecting");
};
let ResolvedBoundVar::Resource { rtidx } = s.resolve_bound_var(*n) else {
unreachable!("rejected while collecting");
};
s.root_mod.resource_index(rtidx as usize).unwrap()
}
fn primitive(v: &Value) -> Option<(&'static str, &'static str)> {
Some(match v {
Value::Bool => ("bool", "Bool"),
Value::S(IntWidth::I8) => ("sbyte", "S8"),
Value::U(IntWidth::I8) => ("byte", "U8"),
Value::S(IntWidth::I16) => ("short", "S16"),
Value::U(IntWidth::I16) => ("ushort", "U16"),
Value::S(IntWidth::I32) => ("int", "S32"),
Value::U(IntWidth::I32) => ("uint", "U32"),
Value::S(IntWidth::I64) => ("long", "S64"),
Value::U(IntWidth::I64) => ("ulong", "U64"),
Value::F(FloatWidth::F32) => ("float", "F32"),
Value::F(FloatWidth::F64) => ("double", "F64"),
Value::Char => ("global::System.Text.Rune", "Char"),
Value::String => ("string", "String"),
_ => return None,
})
}
fn is_u8(v: &Value) -> bool {
matches!(v, Value::U(IntWidth::I8))
}
fn is_option<'b>(s: &mut S<'_, 'b>, v: &Value<'b>) -> bool {
match (v, target(s, v)) {
(Value::Option(_), _) => true,
(_, Some(Target::Alias(def, off))) => alias(s, off, |s| is_option(s, &def)),
_ => false,
}
}
fn is_value_type<'b>(s: &mut S<'_, 'b>, v: &Value<'b>) -> bool {
match v {
Value::String | Value::List(_) | Value::FixList(..) | Value::Own(_) | Value::Borrow(_) => {
false
}
Value::Var(..) => match target(s, v) {
Some(Target::Named(i)) => {
matches!(s.root_mod.decls[i].value, Value::Enum(_) | Value::Flags(_))
}
Some(Target::Alias(def, off)) => alias(s, off, |s| is_value_type(s, &def)),
None => false,
},
_ => true,
}
}
pub(crate) fn ty<'b>(s: &mut S<'_, 'b>, v: &Value<'b>) -> String {
if let Some((ty, _)) = primitive(v) {
return ty.to_string();
}
match v {
Value::Var(..) => match target(s, v) {
Some(Target::Named(i)) => s.root_mod.type_name(&s.root_mod.decls[i]),
Some(Target::Alias(def, off)) => alias(s, off, |s| ty(s, &def)),
None => unreachable!(),
},
Value::Tuple(ts) if ts.len() == 1 => {
format!("global::System.ValueTuple<{}>", ty(s, &ts[0]))
}
Value::Tuple(ts) => {
let types = ts.iter().map(|t| ty(s, t)).collect::<Vec<_>>();
format!("({})", types.join(", "))
}
Value::Option(inner) if is_option(s, inner) => format!("{RT}.Option<{}>", ty(s, inner)),
Value::Option(inner) => format!("{}?", ty(s, inner)),
Value::Result(ok, err) => format!(
"{RT}.Result<{}, {}>",
ty_or_unit(s, ok.as_ref().as_ref()),
ty_or_unit(s, err.as_ref().as_ref())
),
Value::List(inner) | Value::FixList(inner, _) if is_u8(inner) => "byte[]".to_string(),
Value::List(inner) | Value::FixList(inner, _) => format!(
"global::System.Collections.Generic.IReadOnlyList<{}>",
ty(s, inner)
),
Value::Own(h) | Value::Borrow(h) => {
let r = &s.root_mod.resources[resource(s, h)];
format!("global::{}.{}", r.namespace, r.name)
}
_ => unreachable!("rejected while collecting"),
}
}
fn ty_or_unit<'b>(s: &mut S<'_, 'b>, v: Option<&Value<'b>>) -> String {
v.map_or_else(|| format!("{RT}.Unit"), |v| ty(s, v))
}
pub(crate) fn write<'b>(s: &mut S<'_, 'b>, src: &mut Src, v: &Value<'b>, value: &str, w: &str) {
if let Some((_, method)) = primitive(v) {
src.line(format!("{w}.{method}({value});"));
return;
}
match v {
Value::Var(..) => match target(s, v) {
Some(Target::Named(i)) => {
let root = &*s.root_mod;
src.line(format!(
"{}.Write_{}({w}, {value});",
root.codec, root.decls[i].mangled
));
}
Some(Target::Alias(def, off)) => alias(s, off, |s| write(s, src, &def, value, w)),
None => unreachable!(),
},
Value::Tuple(ts) => {
let t = s.root_mod.temp();
src.line(format!("var {t} = {value};"));
for (i, ty) in ts.iter().enumerate() {
write(s, src, ty, &tuple_item(&t, i), w);
}
}
Value::Option(inner) => {
let t = s.root_mod.temp();
src.line(format!("var {t} = {value};"));
let (has_value, payload) = if is_option(s, inner) || is_value_type(s, inner) {
(format!("{t}.HasValue"), format!("{t}.Value"))
} else {
(format!("{t} is not null"), t.clone())
};
src.line(format!("if ({has_value})"));
src.open();
src.line(format!("{w}.U8(1);"));
write(s, src, inner, &payload, w);
src.close();
src.line("else");
src.open();
src.line(format!("{w}.U8(0);"));
src.close();
}
Value::Result(ok, err) => {
let t = s.root_mod.temp();
src.line(format!("var {t} = {value};"));
src.line(format!("if ({t}.IsOk)"));
src.open();
src.line(format!("{w}.U8(0);"));
if let Some(ok) = ok.as_ref() {
write(s, src, ok, &format!("{t}.Ok"), w);
}
src.close();
src.line("else");
src.open();
src.line(format!("{w}.U8(1);"));
if let Some(err) = err.as_ref() {
write(s, src, err, &format!("{t}.Err"), w);
}
src.close();
}
Value::List(inner) if is_u8(inner) => src.line(format!("{w}.Bytes({value});")),
Value::FixList(inner, n) if is_u8(inner) => {
src.line(format!("{w}.FixedBytes({value}, {n});"));
}
Value::List(inner) | Value::FixList(inner, _) => {
let t = s.root_mod.temp();
src.line(format!("var {t} = {value};"));
match v {
Value::FixList(_, n) => {
src.line(format!("{RT}.WireWriter.CheckFixedLength({t}, {n});"));
}
_ => {
src.line(format!(
"global::System.ArgumentNullException.ThrowIfNull({t});"
));
src.line(format!("{w}.Length({t}.Count);"));
}
}
let e = s.root_mod.temp();
src.line(format!("foreach (var {e} in {t})"));
src.open();
write(s, src, inner, &e, w);
src.close();
}
Value::Own(h) => {
let table = s
.root_mod
.resource_table(resource(s, h), &format!("{w}.Resources"));
src.line(format!("{w}.U32({table}.Give({value}, {w}.Scope));"));
}
Value::Borrow(h) => {
let table = s
.root_mod
.resource_table(resource(s, h), &format!("{w}.Resources"));
src.line(format!("{w}.U32({table}.Lend({value}, {w}.Scope!));"));
}
_ => unreachable!("rejected while collecting"),
}
}
pub(crate) fn read<'b>(s: &mut S<'_, 'b>, src: &mut Src, v: &Value<'b>, r: &str) -> String {
if let Value::Var(..) = v
&& let Some(Target::Alias(def, off)) = target(s, v)
{
return alias(s, off, |s| read(s, src, &def, r));
}
let t = s.root_mod.temp();
if let Some((_, method)) = primitive(v) {
src.line(format!("var {t} = {r}.{method}();"));
return t;
}
match v {
Value::Var(..) => {
let Some(Target::Named(i)) = target(s, v) else {
unreachable!()
};
let root = &*s.root_mod;
src.line(format!(
"var {t} = {}.Read_{}(ref {r});",
root.codec, root.decls[i].mangled
));
}
Value::Tuple(ts) => {
let items = ts.iter().map(|ty| read(s, src, ty, r)).collect::<Vec<_>>();
if items.len() == 1 {
src.line(format!("var {t} = new {}({});", ty(s, v), items[0]));
} else {
src.line(format!("var {t} = ({});", items.join(", ")));
}
}
Value::Option(inner) => {
let option_ty = ty(s, v);
let nested = is_option(s, inner);
src.line(format!("{option_ty} {t};"));
src.line(format!("if ({r}.U8() != 0)"));
src.open();
let x = read(s, src, inner, r);
if nested {
src.line(format!("{t} = {option_ty}.Some({x});"));
} else {
src.line(format!("{t} = {x};"));
}
src.close();
src.line("else");
src.open();
if nested {
src.line(format!("{t} = {option_ty}.None;"));
} else {
src.line(format!("{t} = null;"));
}
src.close();
}
Value::Result(ok, err) => {
let result_ty = ty(s, v);
src.line(format!("{result_ty} {t};"));
src.line(format!("if ({r}.U8() == 0)"));
src.open();
let x = read_or_unit(s, src, ok.as_ref().as_ref(), r);
src.line(format!("{t} = {result_ty}.FromOk({x});"));
src.close();
src.line("else");
src.open();
let x = read_or_unit(s, src, err.as_ref().as_ref(), r);
src.line(format!("{t} = {result_ty}.FromErr({x});"));
src.close();
}
Value::List(inner) if is_u8(inner) => src.line(format!("var {t} = {r}.Bytes();")),
Value::FixList(inner, n) if is_u8(inner) => {
src.line(format!("var {t} = {r}.FixedBytes({n});"));
}
Value::List(inner) | Value::FixList(inner, _) => {
let n = s.root_mod.temp();
let capacity = match v {
Value::FixList(_, len) => {
src.line(format!("var {n} = {len};"));
n.clone()
}
_ => {
src.line(format!("var {n} = {r}.Length();"));
String::new()
}
};
let elem = ty(s, inner);
src.line(format!(
"var {t} = new global::System.Collections.Generic.List<{elem}>({capacity});"
));
let i = s.root_mod.temp();
src.line(format!("for (var {i} = 0; {i} < {n}; {i}++)"));
src.open();
let x = read(s, src, inner, r);
src.line(format!("{t}.Add({x});"));
src.close();
}
Value::Own(h) => {
let table = s
.root_mod
.resource_table(resource(s, h), &format!("{r}.Resources"));
src.line(format!("var {t} = {table}.Take({r}.U32());"));
}
Value::Borrow(h) => {
let table = s
.root_mod
.resource_table(resource(s, h), &format!("{r}.Resources"));
src.line(format!("var {t} = {table}.Borrow({r}.U32());"));
}
_ => unreachable!("rejected while collecting"),
}
t
}
fn read_or_unit<'b>(s: &mut S<'_, 'b>, src: &mut Src, v: Option<&Value<'b>>, r: &str) -> String {
match v {
Some(v) => read(s, src, v, r),
None => format!("default({RT}.Unit)"),
}
}
fn at_decl<'c, 'b>(s: &'c mut S<'_, 'b>, i: usize) -> (S<'c, 'b>, Value<'b>, String) {
let d = &s.root_mod.decls[i];
let (var, value, name) = (d.var, d.value.clone(), d.name.clone());
let mut s = s.clone();
s.var_offset = var + 1;
(s, value, name)
}
pub(crate) fn emit_decl(s: &mut S<'_, '_>, i: usize) -> Src {
let (mut s, value, name) = at_decl(s, i);
let s = &mut s;
let mut src = Src::default();
match &value {
Value::Record(fields) => {
src.line(format!("public sealed record {name}"));
src.open();
for (i, f) in fields.iter().enumerate() {
if i > 0 {
src.line("");
}
src.line(format!(
"public required {} {} {{ get; init; }}",
ty(s, &f.ty),
member(f.name.name, &name)
));
}
src.close();
}
Value::Variant(cases) => {
src.line(format!("public abstract record {name}"));
src.open();
src.line(format!("private {name}() {{ }}"));
for c in cases {
let case_name = case(c.name.name, &name);
src.line("");
match &c.ty {
Some(t) => src.line(format!(
"public sealed record {case_name}({} Value) : {name};",
ty(s, t)
)),
None => src.line(format!("public sealed record {case_name} : {name};")),
}
}
src.close();
}
Value::Enum(names) => {
src.line(format!("public enum {name} : uint"));
src.open();
for (i, c) in names.iter().enumerate() {
src.line(format!("{} = {i},", pascal(c.name)));
}
src.close();
}
Value::Flags(flags) => {
src.line("[global::System.Flags]");
src.line(format!("public enum {name} : uint"));
src.open();
if flags.iter().all(|f| pascal(f.name) != "None") {
src.line("None = 0,");
}
for (i, f) in flags.iter().enumerate() {
src.line(format!("{} = 1u << {i},", pascal(f.name)));
}
src.close();
}
_ => unreachable!("only named types are declared"),
}
src
}
pub(crate) fn emit_type_codec(s: &mut S<'_, '_>, src: &mut Src, i: usize) {
let full = s.root_mod.type_name(&s.root_mod.decls[i]);
let mangled = s.root_mod.decls[i].mangled.clone();
let (mut s, value, name) = at_decl(s, i);
let s = &mut s;
src.line(format!(
"public static void Write_{mangled}({RT}.WireWriter w, {full} v)"
));
src.open();
match &value {
Value::Record(fields) => {
src.line("global::System.ArgumentNullException.ThrowIfNull(v);");
for f in fields {
write(
s,
src,
&f.ty,
&format!("v.{}", member(f.name.name, &name)),
"w",
);
}
}
Value::Variant(cases) => {
src.line("switch (v)");
src.open();
for (i, c) in cases.iter().enumerate() {
let case_ty = format!("{full}.{}", case(c.name.name, &name));
match &c.ty {
Some(payload) => {
let x = s.root_mod.temp();
src.line(format!("case {case_ty} {x}:"));
src.indent_lines(|src| {
src.line(format!("w.U32({i});"));
write(s, src, payload, &format!("{x}.Value"), "w");
src.line("break;");
});
}
None => {
src.line(format!("case {case_ty}:"));
src.indent_lines(|src| {
src.line(format!("w.U32({i});"));
src.line("break;");
});
}
}
}
src.line("default:");
src.indent_lines(|src| {
src.line(
"throw new global::System.ArgumentException($\"unknown case {v}\", nameof(v));",
);
});
src.close();
}
Value::Enum(names) => {
src.line(format!("if ((uint)v >= {})", names.len()));
src.open();
src.line("throw new global::System.ArgumentOutOfRangeException(nameof(v));");
src.close();
src.line("w.U32((uint)v);");
}
Value::Flags(flags) => {
let mask = flags_mask(flags.len());
src.line(format!("if (((uint)v & ~{mask}u) != 0)"));
src.open();
src.line("throw new global::System.ArgumentOutOfRangeException(nameof(v));");
src.close();
src.line(format!("w.Flags((uint)v, {});", flags.len().div_ceil(8)));
}
_ => unreachable!("only named types are declared"),
}
src.close();
src.line("");
src.line(format!(
"public static {full} Read_{mangled}(ref {RT}.WireReader r)"
));
src.open();
match &value {
Value::Record(fields) => {
let fields = fields
.iter()
.map(|f| (member(f.name.name, &name), read(s, src, &f.ty, "r")))
.collect::<Vec<_>>();
src.line(format!("return new {full}"));
src.open();
for (field, value) in fields {
src.line(format!("{field} = {value},"));
}
src.close_with("};");
}
Value::Variant(cases) => {
src.line(format!("switch (r.Tag32({}))", cases.len()));
src.open();
for (i, c) in cases.iter().enumerate() {
let case_ty = format!("{full}.{}", case(c.name.name, &name));
src.line(format!("case {i}:"));
src.open();
match &c.ty {
Some(payload) => {
let x = read(s, src, payload, "r");
src.line(format!("return new {case_ty}({x});"));
}
None => src.line(format!("return new {case_ty}();")),
}
src.close();
}
src.line("default:");
src.indent_lines(|src| {
src.line("throw new global::System.InvalidOperationException();");
});
src.close();
}
Value::Enum(names) => {
src.line(format!("return ({full})r.Tag32({});", names.len()));
}
Value::Flags(flags) => {
let mask = flags_mask(flags.len());
src.line(format!(
"return ({full})r.Flags({}, {mask}u);",
flags.len().div_ceil(8)
));
}
_ => unreachable!("only named types are declared"),
}
src.close();
}
fn tuple_item(tuple: &str, i: usize) -> String {
format!("{tuple}.Item{}", i + 1)
}
fn flags_mask(count: usize) -> String {
format!("0x{:X}", u32::MAX >> (32 - count))
}