From cb4aeb9c012c72fc5477a0b1d5be7e27abc79d3d Mon Sep 17 00:00:00 2001 From: nub31 Date: Wed, 25 Feb 2026 21:21:28 +0100 Subject: [PATCH] Add anonymous structs --- compiler/Generator.cs | 6 +++ compiler/ModuleGraph.cs | 1 + compiler/NubType.cs | 101 ++++++++++++++++++++++++++++++++++------ compiler/Parser.cs | 26 +++++++++++ compiler/TypeChecker.cs | 1 + examples/math/math.nub | 36 ++++++++++++-- 6 files changed, 153 insertions(+), 18 deletions(-) diff --git a/compiler/Generator.cs b/compiler/Generator.cs index d6b0668..0324b56 100644 --- a/compiler/Generator.cs +++ b/compiler/Generator.cs @@ -448,6 +448,7 @@ public class Generator NubTypeVoid => "void" + (varName != null ? $" {varName}" : ""), NubTypeBool => "bool" + (varName != null ? $" {varName}" : ""), NubTypeStruct type => $"struct {NameMangler.Mangle(type.Module, type.Name, type)}" + (varName != null ? $" {varName}" : ""), + NubTypeAnonymousStruct type => CTypeAnonymousStruct(type, varName), NubTypeEnum type => $"struct {NameMangler.Mangle(type.Module, type.Name, type)}" + (varName != null ? $" {varName}" : ""), NubTypeEnumVariant type => CType(type.EnumType, varName), NubTypeSInt type => $"int{type.Width}_t" + (varName != null ? $" {varName}" : ""), @@ -458,6 +459,11 @@ public class Generator _ => throw new ArgumentOutOfRangeException(nameof(node), node, null) }; } + + private string CTypeAnonymousStruct(NubTypeAnonymousStruct type, string? varName) + { + return $"struct {{ {string.Join(' ', type.Fields.Select(x => $"{CType(x.Type)} {x.Name};"))} }}{(varName != null ? $" {varName}" : "")}"; + } } internal class IndentedTextWriter diff --git a/compiler/ModuleGraph.cs b/compiler/ModuleGraph.cs index 6559b27..7cbc095 100644 --- a/compiler/ModuleGraph.cs +++ b/compiler/ModuleGraph.cs @@ -192,6 +192,7 @@ public class ModuleGraph { NodeTypeBool => NubTypeBool.Instance, NodeTypeNamed type => ResolveNamedType(type, currentModule), + NodeTypeAnonymousStruct type => NubTypeAnonymousStruct.Get(type.Fields.Select(x => new NubTypeAnonymousStruct.Field(x.Name.Ident, ResolveType(x.Type, currentModule))).ToList()), NodeTypeFunc type => NubTypeFunc.Get(type.Parameters.Select(x => ResolveType(x, currentModule)).ToList(), ResolveType(type.ReturnType, currentModule)), NodeTypePointer type => NubTypePointer.Get(ResolveType(type.To, currentModule)), NodeTypeSInt type => NubTypeSInt.Get(type.Width), diff --git a/compiler/NubType.cs b/compiler/NubType.cs index 46a7040..b3df3ce 100644 --- a/compiler/NubType.cs +++ b/compiler/NubType.cs @@ -123,7 +123,39 @@ public class NubTypeStruct : NubType public string Module { get; } public string Name { get; } - public override string ToString() => $"struct {Module}::{Name}"; + public override string ToString() => $"{Module}::{Name}"; +} + +public class NubTypeAnonymousStruct : NubType +{ + private static readonly Dictionary Cache = new(); + + public static NubTypeAnonymousStruct Get(List fields) + { + var sig = new Signature(fields); + + if (!Cache.TryGetValue(sig, out var func)) + Cache[sig] = func = new NubTypeAnonymousStruct(fields); + + return func; + } + + private NubTypeAnonymousStruct(IReadOnlyList fields) + { + Fields = fields; + } + + public IReadOnlyList Fields { get; } + + public override string ToString() => $"{{ {string.Join(", ", Fields.Select(x => $"{x.Name}: {x.Type}"))} }}"; + + public class Field(string name, NubType type) + { + public string Name { get; } = name; + public NubType Type { get; } = type; + } + + private record Signature(IReadOnlyList Fields); } public class NubTypeEnum : NubType @@ -147,7 +179,7 @@ public class NubTypeEnum : NubType public string Module { get; } public string Name { get; } - public override string ToString() => $"enum {Module}::{Name}"; + public override string ToString() => $"{Module}::{Name}"; } public class NubTypeEnumVariant : NubType @@ -277,7 +309,7 @@ public class TypeEncoder break; case NubTypeStruct st: - sb.Append("T("); + sb.Append("TN("); sb.Append(st.Module); sb.Append(':'); sb.Append(st.Name); @@ -312,6 +344,17 @@ public class TypeEncoder sb.Append(')'); break; + case NubTypeAnonymousStruct s: + sb.Append("TA("); + foreach (var field in s.Fields) + { + sb.Append(field.Name); + sb.Append(':'); + EncodeType(sb, field.Type); + } + sb.Append(')'); + break; + default: throw new NotSupportedException(type.GetType().Name); } @@ -394,23 +437,53 @@ public class TypeDecoder return NubTypeFunc.Get(types.Take(types.Count - 1).ToList(), types.Last()); } - private NubTypeStruct DecodeStruct() + private NubType DecodeStruct() { - var sb = new StringBuilder(); - Expect('('); - while (!TryExpect(':')) - sb.Append(Consume()); + if (TryExpect('A')) + { + var sb = new StringBuilder(); + var fields = new List(); - var module = sb.ToString(); - sb.Clear(); + Expect('('); + while (!TryExpect(')')) + { + while (!TryExpect(':')) + { + sb.Append(Consume()); + } - while (!TryExpect(')')) - sb.Append(Consume()); + var name = sb.ToString(); + sb.Clear(); - var name = sb.ToString(); + var type = DecodeType(); - return NubTypeStruct.Get(module, name); + fields.Add(new NubTypeAnonymousStruct.Field(name, type)); + } + + return NubTypeAnonymousStruct.Get(fields); + } + + if (TryExpect('N')) + { + var sb = new StringBuilder(); + + Expect('('); + while (!TryExpect(':')) + sb.Append(Consume()); + + var module = sb.ToString(); + sb.Clear(); + + while (!TryExpect(')')) + sb.Append(Consume()); + + var name = sb.ToString(); + + return NubTypeStruct.Get(module, name); + } + + throw new Exception("Expected 'A' or 'N'"); } private NubType DecodeEnum() diff --git a/compiler/Parser.cs b/compiler/Parser.cs index 4ab9fac..4b1b1e2 100644 --- a/compiler/Parser.cs +++ b/compiler/Parser.cs @@ -457,6 +457,21 @@ public class Parser return new NodeTypeFunc(TokensFrom(startIndex), parameters, returnType); } + if (TryExpectSymbol(Symbol.OpenCurly)) + { + var fields = new List(); + + while (!TryExpectSymbol(Symbol.CloseCurly)) + { + var name = ExpectIdent(); + ExpectSymbol(Symbol.Colon); + var type = ParseType(); + fields.Add(new NodeTypeAnonymousStruct.Field(name, type)); + } + + return new NodeTypeAnonymousStruct(TokensFrom(startIndex), fields); + } + if (TryExpectIdent(out var ident)) { switch (ident.Ident) @@ -953,6 +968,17 @@ public class NodeTypeNamed(List tokens, List sections) : Node public List Sections { get; } = sections; } +public class NodeTypeAnonymousStruct(List tokens, List fields) : NodeType(tokens) +{ + public List Fields { get; } = fields; + + public class Field(TokenIdent name, NodeType type) + { + public TokenIdent Name { get; } = name; + public NodeType Type { get; } = type; + } +} + public class NodeTypePointer(List tokens, NodeType to) : NodeType(tokens) { public NodeType To { get; } = to; diff --git a/compiler/TypeChecker.cs b/compiler/TypeChecker.cs index ddfbfb5..61e562e 100644 --- a/compiler/TypeChecker.cs +++ b/compiler/TypeChecker.cs @@ -459,6 +459,7 @@ public class TypeChecker { NodeTypeBool => NubTypeBool.Instance, NodeTypeNamed type => ResolveNamedType(type), + NodeTypeAnonymousStruct type => NubTypeAnonymousStruct.Get(type.Fields.Select(x => new NubTypeAnonymousStruct.Field(x.Name.Ident, ResolveType(x.Type))).ToList()), NodeTypeFunc type => NubTypeFunc.Get(type.Parameters.Select(ResolveType).ToList(), ResolveType(type.ReturnType)), NodeTypePointer type => NubTypePointer.Get(ResolveType(type.To)), NodeTypeSInt type => NubTypeSInt.Get(type.Width), diff --git a/examples/math/math.nub b/examples/math/math.nub index 14215c9..de9fe25 100644 --- a/examples/math/math.nub +++ b/examples/math/math.nub @@ -1,9 +1,37 @@ 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: vec2 b: vec3 c: color } +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: { + a: i32 + } + b: { + b: { + c: i32 + } + c: { + d: i32 + } + } +} + export enum message { quit