Files
nub-lang/compiler/NubType.cs
2026-02-10 22:43:03 +01:00

436 lines
11 KiB
C#

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<int, NubTypeUInt> 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<int, NubTypeSInt> 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<Field>? _resolvedFields;
public IReadOnlyList<Field> Fields => _resolvedFields ?? throw new InvalidOperationException();
public NubTypeStruct(string module, string name, bool packed)
{
Module = module;
Name = name;
Packed = packed;
}
public void ResolveFields(IReadOnlyList<Field> fields)
{
if (_resolvedFields != null)
throw new InvalidOperationException($"{Name} already resolved");
_resolvedFields = fields;
}
public override string ToString() => _resolvedFields == null ? $"struct {Module}::{Name} <unresolved>" : $"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<NubType, NubTypePointer> 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<Signature, NubTypeFunc> Cache = new();
public static NubTypeFunc Get(List<NubType> 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<NubType> Parameters { get; }
public NubType ReturnType { get; }
public string MangledName(string name, string module) => SymbolNameGen.Exported(name, module, this);
private NubTypeFunc(List<NubType> parameters, NubType returnType)
{
Parameters = parameters;
ReturnType = returnType;
}
public override string ToString() => $"func({string.Join(' ', Parameters)}): {ReturnType}";
private readonly record struct Signature(IReadOnlyList<NubType> 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<NubTypeStruct.Field>();
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<NubType>();
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();
}
}