using System.Net; using System.Text; namespace Compiler; public abstract class NubType { public abstract override string ToString(); } public sealed class NubTypeVoid : NubType { public static readonly NubTypeVoid Instance = new(); private NubTypeVoid() { } public override string ToString() => "void"; } public sealed class NubTypeUInt : NubType { private static readonly Dictionary Cache = new(); public static NubTypeUInt Get(int width) { if (!Cache.TryGetValue(width, out var type)) Cache[width] = type = new NubTypeUInt(width); return type; } public int Width { get; } private NubTypeUInt(int width) { Width = width; } public override string ToString() => $"u{Width}"; } public sealed class NubTypeSInt : NubType { private static readonly Dictionary Cache = new(); public static NubTypeSInt Get(int width) { if (!Cache.TryGetValue(width, out var type)) Cache[width] = type = new NubTypeSInt(width); return type; } public int Width { get; } private NubTypeSInt(int width) { Width = width; } public override string ToString() => $"i{Width}"; } public sealed class NubTypeBool : NubType { public static readonly NubTypeBool Instance = new(); private NubTypeBool() { } public override string ToString() => "bool"; } public sealed class NubTypeString : NubType { public static readonly NubTypeString Instance = new(); private NubTypeString() { } public override string ToString() => "string"; } public sealed class NubTypeStruct : NubType { public string Name { get; } public string Module { get; } public bool Packed { get; } private IReadOnlyList? _resolvedFields; public IReadOnlyList Fields => _resolvedFields ?? throw new InvalidOperationException(); public NubTypeStruct(string module, string name, bool packed) { Module = module; Name = name; Packed = packed; } public void ResolveFields(IReadOnlyList fields) { if (_resolvedFields != null) throw new InvalidOperationException($"{Name} already resolved"); _resolvedFields = fields; } public override string ToString() => _resolvedFields == null ? $"struct {Module}::{Name} " : $"struct {Module}::{Name} {{ {string.Join(' ', Fields.Select(f => $"{f.Name}: {f.Type}"))} }}"; public sealed class Field(string name, NubType type) { public string Name { get; } = name; public NubType Type { get; } = type; } } public sealed class NubTypePointer : NubType { private static readonly Dictionary Cache = new(); public static NubTypePointer Get(NubType to) { if (!Cache.TryGetValue(to, out var ptr)) Cache[to] = ptr = new NubTypePointer(to); return ptr; } public NubType To { get; } private NubTypePointer(NubType to) { To = to; } public override string ToString() => $"^{To}"; } public sealed class NubTypeFunc : NubType { private static readonly Dictionary Cache = new(); public static NubTypeFunc Get(List parameters, NubType returnType) { var sig = new Signature(parameters, returnType); if (!Cache.TryGetValue(sig, out var func)) Cache[sig] = func = new NubTypeFunc(parameters, returnType); return func; } public IReadOnlyList Parameters { get; } public NubType ReturnType { get; } public string MangledName(string name, string module) => SymbolNameGen.Exported(name, module, this); private NubTypeFunc(List parameters, NubType returnType) { Parameters = parameters; ReturnType = returnType; } public override string ToString() => $"func({string.Join(' ', Parameters)}): {ReturnType}"; private readonly record struct Signature(IReadOnlyList Parameters, NubType ReturnType); } static class TypeMangler { public static string Encode(NubType type) { var sb = new StringBuilder(); Encode(type, sb); return sb.ToString(); } private static void Encode(NubType type, StringBuilder sb) { switch (type) { case NubTypeVoid: sb.Append('V'); break; case NubTypeBool: sb.Append('B'); break; case NubTypeUInt u: sb.Append("U(").Append(u.Width).Append(')'); break; case NubTypeSInt s: sb.Append("I(").Append(s.Width).Append(')'); break; case NubTypeString: sb.Append('S'); break; case NubTypePointer p: sb.Append("P("); Encode(p.To, sb); sb.Append(')'); break; case NubTypeStruct st: sb.Append("T("); sb.Append(st.Module).Append("::").Append(st.Name); sb.Append(',').Append(st.Packed ? '1' : '0'); sb.Append('{'); for (int i = 0; i < st.Fields.Count; i++) { var field = st.Fields[i]; sb.Append(field.Name).Append(':'); Encode(field.Type, sb); if (i < st.Fields.Count - 1) sb.Append(','); } sb.Append("})"); break; case NubTypeFunc fn: sb.Append("F("); foreach (var parameter in fn.Parameters) { sb.Append(Encode(parameter)); sb.Append(','); } sb.Append(Encode(fn.ReturnType)); sb.Append(')'); break; default: throw new NotSupportedException(type.GetType().Name); } } public static NubType Decode(string encoded) { int pos = 0; return Parse(encoded, ref pos); } private static NubType Parse(string s, ref int pos) { if (pos >= s.Length) throw new InvalidOperationException("Unexpected end of string"); char c = s[pos++]; return c switch { 'V' => NubTypeVoid.Instance, 'B' => NubTypeBool.Instance, 'S' => NubTypeString.Instance, 'U' => ParseUInt(s, ref pos), 'I' => ParseSInt(s, ref pos), 'P' => ParsePointer(s, ref pos), 'T' => ParseStruct(s, ref pos), 'F' => ParseFunc(s, ref pos), _ => throw new NotSupportedException($"Unknown type code '{c}' at position {pos - 1}") }; } private static NubTypeUInt ParseUInt(string s, ref int pos) { ExpectChar(s, ref pos, '('); int width = ReadNumber(s, ref pos); ExpectChar(s, ref pos, ')'); return NubTypeUInt.Get(width); } private static NubTypeSInt ParseSInt(string s, ref int pos) { ExpectChar(s, ref pos, '('); int width = ReadNumber(s, ref pos); ExpectChar(s, ref pos, ')'); return NubTypeSInt.Get(width); } private static NubTypePointer ParsePointer(string s, ref int pos) { ExpectChar(s, ref pos, '('); var to = Parse(s, ref pos); ExpectChar(s, ref pos, ')'); return NubTypePointer.Get(to); } private static NubTypeStruct ParseStruct(string s, ref int pos) { ExpectChar(s, ref pos, '('); int start = pos; while (pos < s.Length && s[pos] != ',' && s[pos] != '{') pos++; var fullName = s[start..pos]; var parts = fullName.Split("::"); if (parts.Length != 2) throw new InvalidOperationException($"Invalid struct name: {fullName}"); string module = parts[0], name = parts[1]; bool packed = false; if (s[pos] == ',') { pos += 1; packed = s[pos += 1] == '1'; } var st = new NubTypeStruct(module, name, packed); ExpectChar(s, ref pos, '{'); var fields = new List(); while (s[pos] != '}') { int nameStart = pos; while (s[pos] != ':') pos += 1; string fieldName = s[nameStart..pos]; pos += 1; var fieldType = Parse(s, ref pos); fields.Add(new NubTypeStruct.Field(fieldName, fieldType)); if (s[pos] == ',') pos += 1; } ExpectChar(s, ref pos, '}'); ExpectChar(s, ref pos, ')'); st.ResolveFields(fields); return st; } private static NubTypeFunc ParseFunc(string s, ref int pos) { ExpectChar(s, ref pos, '('); var parameters = new List(); while (true) { if (s[pos] == ')') { pos++; break; } var param = Parse(s, ref pos); parameters.Add(param); if (s[pos] == ',') { pos += 1; } else if (s[pos] == ')') { pos += 1; break; } else { throw new InvalidOperationException($"Unexpected char '{s[pos]}' in function type at {pos}"); } } if (parameters.Count == 0) throw new InvalidOperationException("Function must have a return type"); var returnType = parameters[^1]; var paramTypes = parameters.Take(parameters.Count - 1).ToList(); return NubTypeFunc.Get(paramTypes, returnType); } private static void ExpectChar(string s, ref int pos, char expected) { if (pos >= s.Length || s[pos] != expected) throw new InvalidOperationException($"Expected '{expected}' at position {pos}"); pos += 1; } private static int ReadNumber(string s, ref int pos) { int start = pos; while (pos < s.Length && char.IsDigit(s[pos])) pos += 1; if (start == pos) throw new InvalidOperationException($"Expected number at position {start}"); return int.Parse(s[start..pos]); } } static class Hashing { public static ulong Fnv1a64(string text) { const ulong offset = 14695981039346656037UL; const ulong prime = 1099511628211UL; ulong hash = offset; foreach (var c in Encoding.UTF8.GetBytes(text)) { hash ^= c; hash *= prime; } return hash; } } static class SymbolNameGen { public static string Exported(string module, string function, NubType type) { var canonical = TypeMangler.Encode(type); var hash = Hashing.Fnv1a64(canonical); return $"nub_{Sanitize(module)}_{Sanitize(function)}_{hash:x16}"; } private static string Sanitize(string s) { var sb = new StringBuilder(s.Length); foreach (var c in s) { if (char.IsLetterOrDigit(c)) sb.Append(c); else sb.Append('_'); } return sb.ToString(); } }