using System.Text; namespace Compiler; public abstract class NubType { public abstract override string ToString(); } public class NubTypeVoid : NubType { public static readonly NubTypeVoid Instance = new(); private NubTypeVoid() { } public override string ToString() => "void"; } public 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 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 class NubTypeBool : NubType { public static readonly NubTypeBool Instance = new(); private NubTypeBool() { } public override string ToString() => "bool"; } public class NubTypeString : NubType { public static readonly NubTypeString Instance = new(); private NubTypeString() { } public override string ToString() => "string"; } public 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 class Field(string name, NubType type) { public string Name { get; } = name; public NubType Type { get; } = type; } } public 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 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 TypeEncoder { 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); } } } class TypeDecoder { public static NubType Decode(string encoded) { return new TypeDecoder(encoded).Decode(); } private TypeDecoder(string encoded) { this.encoded = encoded; } private readonly string encoded; private int pos; private NubType Decode() { return Parse(); } private NubType Parse() { if (pos >= encoded.Length) throw new InvalidOperationException("Unexpected end of string"); char c = encoded[pos++]; return c switch { 'V' => NubTypeVoid.Instance, 'B' => NubTypeBool.Instance, 'S' => NubTypeString.Instance, 'U' => ParseUInt(), 'I' => ParseSInt(), 'P' => ParsePointer(), 'T' => ParseStruct(), 'F' => ParseFunc(), _ => throw new NotSupportedException($"Unknown type code '{c}' at position {pos - 1}") }; } private NubTypeUInt ParseUInt() { ExpectChar('('); int width = ReadNumber(); ExpectChar(')'); return NubTypeUInt.Get(width); } private NubTypeSInt ParseSInt() { ExpectChar('('); int width = ReadNumber(); ExpectChar(')'); return NubTypeSInt.Get(width); } private NubTypePointer ParsePointer() { ExpectChar('('); var to = Parse(); ExpectChar(')'); return NubTypePointer.Get(to); } private NubTypeStruct ParseStruct() { ExpectChar('('); int start = pos; while (pos < encoded.Length && encoded[pos] != ',' && encoded[pos] != '{') pos++; var fullName = encoded[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 (encoded[pos] == ',') { pos += 1; packed = encoded[pos += 1] == '1'; } var st = new NubTypeStruct(module, name, packed); ExpectChar('{'); var fields = new List(); while (encoded[pos] != '}') { int nameStart = pos; while (encoded[pos] != ':') pos += 1; string fieldName = encoded[nameStart..pos]; pos += 1; var fieldType = Parse(); fields.Add(new NubTypeStruct.Field(fieldName, fieldType)); if (encoded[pos] == ',') pos += 1; } ExpectChar('}'); ExpectChar(')'); st.ResolveFields(fields); return st; } private NubTypeFunc ParseFunc() { ExpectChar('('); var parameters = new List(); while (true) { if (encoded[pos] == ')') { pos++; break; } var param = Parse(); parameters.Add(param); if (encoded[pos] == ',') { pos += 1; } else if (encoded[pos] == ')') { pos += 1; break; } else { throw new InvalidOperationException($"Unexpected char '{encoded[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 void ExpectChar(char expected) { if (pos >= encoded.Length || encoded[pos] != expected) throw new InvalidOperationException($"Expected '{expected}' at position {pos}"); pos += 1; } private int ReadNumber() { int start = pos; while (pos < encoded.Length && char.IsDigit(encoded[pos])) pos += 1; if (start == pos) throw new InvalidOperationException($"Expected number at position {start}"); return int.Parse(encoded[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 = TypeEncoder.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(); } }