From 21a095f6914aae2c0c17b8a63e76923b79086092 Mon Sep 17 00:00:00 2001 From: nub31 Date: Wed, 11 Feb 2026 21:47:51 +0100 Subject: [PATCH] .... --- compiler/ModuleGraph.cs | 9 +- compiler/NubType.cs | 429 +++++++++++++++++++++++++--------------- examples/build.sh | 7 +- examples/math/math.nub | 5 + 4 files changed, 284 insertions(+), 166 deletions(-) diff --git a/compiler/ModuleGraph.cs b/compiler/ModuleGraph.cs index 65679e2..109a2fb 100644 --- a/compiler/ModuleGraph.cs +++ b/compiler/ModuleGraph.cs @@ -198,7 +198,7 @@ public class ModuleGraph foreach (var structDef in ast.Definitions.OfType()) { - var type = new NubTypeStruct(module.Name, structDef.Name.Ident, structDef.Packed); + var type = NubTypeStruct.CreateWithoutFields(structDef.Packed); var info = new Module.CustomTypeInfo(type, structDef.Exported, Module.Source.Ast); module.AddCustomType(structDef.Name.Ident, info); } @@ -214,8 +214,11 @@ public class ModuleGraph if (!module.TryResolveCustomType(structDef.Name.Ident, true, out var customType)) throw new UnreachableException($"{nameof(customType)} should always be registered"); - var fields = structDef.Fields.Select(f => new NubTypeStruct.Field(f.Name.Ident, ResolveType(f.Type, module.Name))).ToList(); - ((NubTypeStruct)customType).ResolveFields(fields); + if (customType is NubTypeStruct structType) + { + var fields = structDef.Fields.Select(f => new NubTypeStruct.Field(f.Name.Ident, ResolveType(f.Type, module.Name))).ToList(); + structType.SetFields(fields); + } } } diff --git a/compiler/NubType.cs b/compiler/NubType.cs index c758090..91683fa 100644 --- a/compiler/NubType.cs +++ b/compiler/NubType.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Text; namespace Compiler; @@ -86,36 +87,61 @@ public class NubTypeString : NubType public class NubTypeStruct : NubType { - public string Name { get; } - public string Module { get; } - public bool Packed { get; } + private static readonly Dictionary Cache = new(); - private IReadOnlyList? _resolvedFields; - - public IReadOnlyList Fields => _resolvedFields ?? throw new InvalidOperationException(); - - public NubTypeStruct(string module, string name, bool packed) + public static NubTypeStruct Get(bool packed, List fields) + { + var sig = new Signature(packed, fields); + + if (!Cache.TryGetValue(sig, out var sturctType)) + Cache[sig] = sturctType = new NubTypeStruct(packed, fields); + + return sturctType; + } + + private NubTypeStruct(bool packed, List fields) + { + Packed = packed; + this.fields = fields; + } + + private NubTypeStruct(bool packed) { - Module = module; - Name = name; Packed = packed; } - public void ResolveFields(IReadOnlyList fields) - { - if (_resolvedFields != null) - throw new InvalidOperationException($"{Name} already resolved"); + public bool Packed { get; } - _resolvedFields = fields; + private IReadOnlyList? fields; + public IReadOnlyList Fields + { + get + { + if (fields == null) + throw new InvalidOperationException("Fields has not been set"); + + return 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 static NubTypeStruct CreateWithoutFields(bool packed) { - public string Name { get; } = name; - public NubType Type { get; } = type; + return new NubTypeStruct(packed); } + + public void SetFields(List fields) + { + if (this.fields != null) + throw new InvalidOperationException("Fields can only be set once"); + + this.fields = fields; + Cache[new Signature(Packed, this.fields)] = this; + } + + public override string ToString() => $"struct {{ {string.Join(' ', Fields.Select(f => $"{f.Name}: {f.Type}"))} }}"; + + public record Field(string Name, NubType Type); + private record Signature(bool Packed, IReadOnlyList Fields); } public class NubTypePointer : NubType @@ -167,19 +193,38 @@ public class NubTypeFunc : NubType public override string ToString() => $"func({string.Join(' ', Parameters)}): {ReturnType}"; - private readonly record struct Signature(IReadOnlyList Parameters, NubType ReturnType); + private record Signature(IReadOnlyList Parameters, NubType ReturnType); } -static class TypeEncoder +public class TypeEncoder { public static string Encode(NubType type) + { + return new TypeEncoder().EncodeRoot(type); + } + + private TypeEncoder() + { + } + + private Dictionary structDefinitions = new(); + + private string EncodeRoot(NubType type) { var sb = new StringBuilder(); - Encode(type, sb); + + EncodeType(sb, type); + + foreach (var definition in structDefinitions.Values.OrderBy(x => x.Index)) + { + Debug.Assert(definition.Encoded != null); + sb.Insert(0, definition.Encoded); + } + return sb.ToString(); } - private static void Encode(NubType type, StringBuilder sb) + private void EncodeType(StringBuilder sb, NubType type) { switch (type) { @@ -192,11 +237,15 @@ static class TypeEncoder break; case NubTypeUInt u: - sb.Append("U(").Append(u.Width).Append(')'); + sb.Append($"U("); + sb.Append(u.Width); + sb.Append(')'); break; case NubTypeSInt s: - sb.Append("I(").Append(s.Width).Append(')'); + sb.Append($"I("); + sb.Append(s.Width); + sb.Append(')'); break; case NubTypeString: @@ -205,34 +254,21 @@ static class TypeEncoder case NubTypePointer p: sb.Append("P("); - Encode(p.To, sb); + EncodeType(sb, p.To); 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("})"); + sb.Append(GetOrCreateStructDefinition(st)); break; case NubTypeFunc fn: sb.Append("F("); - foreach (var parameter in fn.Parameters) + for (int i = 0; i < fn.Parameters.Count; i++) { - sb.Append(Encode(parameter)); - sb.Append(','); + EncodeType(sb, fn.Parameters[i]); } - sb.Append(Encode(fn.ReturnType)); + EncodeType(sb, fn.ReturnType); sb.Append(')'); break; @@ -240,13 +276,48 @@ static class TypeEncoder throw new NotSupportedException(type.GetType().Name); } } + + private string GetOrCreateStructDefinition(NubTypeStruct st) + { + if (!structDefinitions.TryGetValue(st, out var definition)) + { + definition = new Definition(structDefinitions.Count); + structDefinitions[st] = definition; + + var sb = new StringBuilder(); + + sb.Append("D("); + sb.Append(st.Packed ? '1' : '0'); + sb.Append('{'); + for (var i = 0; i < st.Fields.Count; i++) + { + var field = st.Fields[i]; + sb.Append(field.Name); + sb.Append(':'); + EncodeType(sb, field.Type); + } + sb.Append('}'); + sb.Append(')'); + + var encoded = sb.ToString(); + definition.Encoded = encoded; + } + + return $"T({definition.Index})"; + } + + private class Definition(int index) + { + public int Index { get; } = index; + public string? Encoded { get; set; } + } } -class TypeDecoder +public class TypeDecoder { public static NubType Decode(string encoded) { - return new TypeDecoder(encoded).Decode(); + return new TypeDecoder(encoded).DecodeRoot(); } private TypeDecoder(string encoded) @@ -255,158 +326,198 @@ class TypeDecoder } private readonly string encoded; - private int pos; + private int index; + private readonly List structDefinitions = new(); - private NubType Decode() + private NubType DecodeRoot() { - return Parse(); + // First pass: Collect all declarations + while (TryExpect('D')) + { + Expect('('); + var packedChar = Consume(); + if (packedChar is not '1' and not '0') + throw new Exception("Expected '0' or '1' for struct packing"); + + Expect('{'); + while (TryPeek(out var c)) + { + Consume(); + if (c == '}') + break; + } + + Expect(')'); + + structDefinitions.Add(NubTypeStruct.CreateWithoutFields(packedChar == '1')); + } + + index = 0; + var defIndex = 0; + + // Second pass: Set field types + while (TryExpect('D')) + { + var fields = new List(); + + Consume(); + Consume(); + Consume(); + + while (!TryExpect('}')) + { + var sb = new StringBuilder(); + + while (!TryExpect(':')) + { + sb.Append(Consume()); + } + + var type = DecodeType(); + + fields.Add(new NubTypeStruct.Field(sb.ToString(), type)); + } + Expect(')'); + + structDefinitions[defIndex].SetFields(fields); + + defIndex += 1; + } + + return DecodeType(); } - private NubType Parse() + private NubType DecodeType() { - if (pos >= encoded.Length) - throw new InvalidOperationException("Unexpected end of string"); - - char c = encoded[pos++]; - return c switch + var start = Consume(); + return start switch { 'V' => NubTypeVoid.Instance, 'B' => NubTypeBool.Instance, + 'U' => DecodeUInt(), + 'I' => DecodeSInt(), 'S' => NubTypeString.Instance, - 'U' => ParseUInt(), - 'I' => ParseSInt(), - 'P' => ParsePointer(), - 'T' => ParseStruct(), - 'F' => ParseFunc(), - _ => throw new NotSupportedException($"Unknown type code '{c}' at position {pos - 1}") + 'P' => DecodePointer(), + 'F' => DecodeFunc(), + 'T' => DecodeStruct(), + _ => throw new Exception($"'{start}' is not a valid start to a type") }; } - private NubTypeUInt ParseUInt() + private NubTypeUInt DecodeUInt() { - ExpectChar('('); - int width = ReadNumber(); - ExpectChar(')'); + Expect('('); + var width = ExpectInt(); + Expect(')'); return NubTypeUInt.Get(width); } - private NubTypeSInt ParseSInt() + private NubTypeSInt DecodeSInt() { - ExpectChar('('); - int width = ReadNumber(); - ExpectChar(')'); + Expect('('); + var width = ExpectInt(); + Expect(')'); return NubTypeSInt.Get(width); } - private NubTypePointer ParsePointer() + private NubTypePointer DecodePointer() { - ExpectChar('('); - var to = Parse(); - ExpectChar(')'); + Expect('('); + var to = DecodeType(); + Expect(')'); return NubTypePointer.Get(to); } - private NubTypeStruct ParseStruct() + private NubTypeFunc DecodeFunc() { - 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}"); + var types = new List(); - string module = parts[0], name = parts[1]; - - bool packed = false; - if (encoded[pos] == ',') + Expect('('); + while (!TryExpect(')')) { - pos += 1; - packed = encoded[pos += 1] == '1'; + types.Add(DecodeType()); } - 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; + return NubTypeFunc.Get(types.Take(types.Count - 1).ToList(), types.Last()); } - private NubTypeFunc ParseFunc() + private NubTypeStruct DecodeStruct() { - ExpectChar('('); - var parameters = new List(); - while (true) + Expect('('); + var index = ExpectInt(); + Expect(')'); + return structDefinitions[index]; + } + + private bool TryPeek(out char c) + { + if (index >= encoded.Length) { - if (encoded[pos] == ')') - { - pos++; + c = '\0'; + return false; + } + + c = encoded[index]; + return true; + } + + private bool TryConsume(out char c) + { + if (index >= encoded.Length) + { + c = '\0'; + return false; + } + + c = encoded[index]; + index += 1; + + return true; + } + + private char Consume() + { + if (!TryConsume(out var c)) + throw new Exception("Unexpected end of string"); + + return c; + } + + private bool TryExpect(char c) + { + if (index >= encoded.Length) + return false; + + if (encoded[index] != c) + return false; + + Consume(); + return true; + } + + private void Expect(char c) + { + if (!TryExpect(c)) + throw new Exception($"Expected '{c}'"); + } + + private int ExpectInt() + { + var buf = string.Empty; + + while (TryPeek(out var c)) + { + if (!char.IsDigit(c)) 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}"); - } + buf += Consume(); } - 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]); + return int.Parse(buf); } } -static class Hashing +public static class Hashing { public static ulong Fnv1a64(string text) { @@ -424,7 +535,7 @@ static class Hashing } } -static class SymbolNameGen +public static class SymbolNameGen { public static string Exported(string module, string function, NubType type) { diff --git a/examples/build.sh b/examples/build.sh index 9c7684e..60e3eea 100755 --- a/examples/build.sh +++ b/examples/build.sh @@ -1,11 +1,10 @@ pushd math - dotnet run --project ../../compiler math.nub --type=lib - +pushd .build +unzip out.nublib +popd popd pushd program - dotnet run --project ../../compiler main.nub ../math/.build/out.nublib - popd \ No newline at end of file diff --git a/examples/math/math.nub b/examples/math/math.nub index d1ca09c..0f28512 100644 --- a/examples/math/math.nub +++ b/examples/math/math.nub @@ -1,5 +1,10 @@ module math +struct vec2 { x: i32 y: i32 } +struct vec3 { x: i32 y: i32 z: i32 } +struct color { r: i32 g: i32 b: i32 a: i32 } +struct example { a: math::vec2 b: math::vec3 c: math::color } + export func add(a: i32 b: i32): i32 { return a + b