using System.Diagnostics; using System.Text; namespace Compiler; public class Generator { public static string Emit(List functions, ModuleGraph moduleGraph, string? entryPoint) { return new Generator(functions, moduleGraph, entryPoint).Emit(); } private Generator(List functions, ModuleGraph moduleGraph, string? entryPoint) { this.functions = functions; this.moduleGraph = moduleGraph; this.entryPoint = entryPoint; } const string NUB_H_CONTENTS = """ #include #include #include typedef struct { char *data; size_t length; size_t ref; uint32_t flags; } string; typedef string *string_ptr; #define FLAG_STRING_LITERAL 1 static inline void string_rc_inc(string_ptr str) { if (str == NULL) return; if (str->flags & FLAG_STRING_LITERAL) return; str->ref += 1; } static inline void string_rc_dec(string_ptr str) { if (str == NULL) return; if (str->flags & FLAG_STRING_LITERAL) return; if (str->ref == 0) return; str->ref -= 1; if (str->ref == 0) { free(str->data); free(str); } } static inline string_ptr string_concat(string_ptr left, string_ptr right) { size_t new_length = left->length + right->length; string_ptr result = (string_ptr)malloc(sizeof(string)); result->data = (char*)malloc(new_length + 1); memcpy(result->data, left->data, left->length); memcpy(result->data + left->length, right->data, right->length); result->data[new_length] = '\0'; result->length = new_length; result->ref = 1; result->flags = 0; return result; } static inline string_ptr string_from_cstr(char *cstr) { size_t len = strlen(cstr); string_ptr result = (string_ptr)malloc(sizeof(string)); result->data = (char*)malloc(len + 1); memcpy(result->data, cstr, len + 1); result->length = len; result->ref = 1; result->flags = 0; return result; } #define da_append(xs, x) \ do \ { \ if ((xs)->count >= (xs)->capacity) \ { \ if ((xs)->capacity == 0) \ (xs)->capacity = 256; \ else \ (xs)->capacity *= 2; \ (xs)->items = realloc((xs)->items, (xs)->capacity * sizeof(*(xs)->items)); \ } \ \ (xs)->items[(xs)->count++] = (x); \ } while (0) """; private readonly List functions; private readonly ModuleGraph moduleGraph; private readonly string? entryPoint; private IndentedTextWriter writer = new(); private readonly Dictionary referencedStringLiterals = []; private readonly HashSet emittedTypes = []; private readonly Stack scopes = new(); private int tmpNameIndex = 0; private string Emit() { var outPath = ".build"; var fileName = "out.c"; if (entryPoint != null) { writer.WriteLine("int main(int argc, char *argv[])"); writer.WriteLine("{"); using (writer.Indent()) { writer.WriteLine($"return {entryPoint}();"); } writer.WriteLine("}"); writer.WriteLine(); } foreach (var function in functions) { if (!moduleGraph.TryResolveIdentifier(function.Module, function.Name.Ident, true, out var info)) throw new UnreachableException($"Module graph does not have info about the function {function.Module}::{function.Name.Ident}. This should have been caught earlier"); if (info.Source == Module.DefinitionSource.Internal && !info.Extern && !info.Exported) writer.Write("static "); var parameters = function.Parameters.Select(x => $"{TypeName(x.Type)} {x.Name.Ident}"); writer.WriteLine($"{TypeName(function.ReturnType)} {info.MangledName}({string.Join(", ", parameters)})"); writer.WriteLine("{"); using (writer.Indent()) { PushScope(); EmitStatement(function.Body); PopScope(); } writer.WriteLine("}"); writer.WriteLine(); } var definitions = writer.ToString(); writer = new IndentedTextWriter(); foreach (var module in moduleGraph.GetModules()) { foreach (var (name, info) in module.GetIdentifiers()) { if (info.Source == Module.DefinitionSource.Internal || info.Exported) { if (info.Source == Module.DefinitionSource.Imported || info.Extern) writer.Write("extern "); else if (info.Source == Module.DefinitionSource.Internal && !info.Extern && !info.Exported) writer.Write("static "); if (info.Type is NubTypeFunc fn) writer.WriteLine($"{TypeName(fn.ReturnType)} {info.MangledName}({string.Join(", ", fn.Parameters.Select(TypeName))});"); else writer.WriteLine($"{TypeName(info.Type)} {info.MangledName};"); writer.WriteLine(); } } } foreach (var (name, value) in referencedStringLiterals) { writer.WriteLine ( $$""" static string {{name}} = (string){ .data = "{{value}}", .length = {{Encoding.UTF8.GetByteCount(value)}}, .ref = 0, .flags = FLAG_STRING_LITERAL }; """ ); } var declarations = writer.ToString(); writer = new IndentedTextWriter(); while (emittedTypes.Count != typeNames.Count) { var nextTypes = typeNames.Keys.ToArray(); foreach (var type in nextTypes) { EmitTypeDefinitionIfNotEmitted(type); } } var types = writer.ToString(); var sb = new StringBuilder(); sb.AppendLine("#include \"nub.h\""); sb.AppendLine(); sb.AppendLine(types); sb.AppendLine(declarations); sb.AppendLine(definitions); Directory.CreateDirectory(outPath); var filePath = Path.Combine(outPath, fileName); File.WriteAllText(Path.Combine(outPath, "nub.h"), NUB_H_CONTENTS); File.WriteAllText(filePath, sb.ToString()); return filePath; } private void EmitTypeDefinitionIfNotEmitted(NubType type) { if (emittedTypes.Contains(type)) return; emittedTypes.Add(type); var name = TypeName(type); switch (type) { case NubTypeStruct structType: { if (!moduleGraph.TryResolveType(structType.Module, structType.Name, true, out var info) || info is not Module.TypeInfoStruct structInfo) throw new UnreachableException(); foreach (var field in structInfo.Fields) EmitTypeDefinitionIfNotEmitted(field.Type); if (structInfo.Packed) writer.Write("__attribute__((__packed__)) "); writer.WriteLine("typedef struct"); writer.WriteLine("{"); using (writer.Indent()) { foreach (var field in structInfo.Fields) { writer.WriteLine($"{TypeName(field.Type)} {field.Name};"); } } writer.WriteLine($"}} {name};"); writer.WriteLine(); break; } case NubTypeAnonymousStruct anonymousStructType: { foreach (var field in anonymousStructType.Fields) EmitTypeDefinitionIfNotEmitted(field.Type); writer.WriteLine("typedef struct"); writer.WriteLine("{"); using (writer.Indent()) { foreach (var field in anonymousStructType.Fields) { writer.WriteLine($"{TypeName(field.Type)} {field.Name};"); } } writer.WriteLine($"}} {name};"); writer.WriteLine(); break; } case NubTypeEnum enumType: { if (!moduleGraph.TryResolveType(enumType.Module, enumType.Name, true, out var info) || info is not Module.TypeInfoEnum enumInfo) throw new UnreachableException(); foreach (var variant in enumInfo.Variants) { if (variant.Type is not null) { EmitTypeDefinitionIfNotEmitted(variant.Type); } } writer.WriteLine("typedef struct"); writer.WriteLine("{"); using (writer.Indent()) { writer.WriteLine("uint32_t tag;"); writer.WriteLine("union"); writer.WriteLine("{"); using (writer.Indent()) { foreach (var variant in enumInfo.Variants) { if (variant.Type is not null) { writer.WriteLine($"{TypeName(variant.Type)} {variant.Name};"); } } } writer.WriteLine("};"); } writer.WriteLine($"}} {name};"); writer.WriteLine(); break; } case NubTypeEnumVariant variantType: { EmitTypeDefinitionIfNotEmitted(variantType.EnumType); break; } case NubTypePointer pointerType: { EmitTypeDefinitionIfNotEmitted(pointerType.To); writer.WriteLine($"typedef {TypeName(pointerType.To)} *{name};"); break; } case NubTypeFunc funcType: { EmitTypeDefinitionIfNotEmitted(funcType.ReturnType); foreach (var parameterType in funcType.Parameters) EmitTypeDefinitionIfNotEmitted(parameterType); writer.WriteLine($"typedef {TypeName(funcType.ReturnType)} (*)({string.Join(", ", funcType.Parameters.Select(TypeName))}) {name};"); break; } case NubTypeArray arrayType: { EmitTypeDefinitionIfNotEmitted(arrayType.ElementType); var backingName = Tmp(); writer.WriteLine("typedef struct"); writer.WriteLine("{"); using (writer.Indent()) { writer.WriteLine("size_t count;"); writer.WriteLine("size_t capacity;"); writer.WriteLine($"{TypeName(arrayType.ElementType)} *items;"); writer.WriteLine("uint32_t ref;"); } writer.WriteLine($"}} {backingName};"); writer.WriteLine(); writer.WriteLine($"typedef {backingName} *{name};"); writer.WriteLine(); writer.WriteLine($"static inline void {name}_rc_inc({name} array)"); writer.WriteLine("{"); using (writer.Indent()) { writer.WriteLine("if (array == NULL) return;"); writer.WriteLine("array->ref += 1;"); } writer.WriteLine("}"); writer.WriteLine(); writer.WriteLine($"static inline void {name}_rc_dec({name} array)"); writer.WriteLine("{"); using (writer.Indent()) { writer.WriteLine("if (array == NULL) return;"); writer.WriteLine("if (array->ref == 0) return;"); writer.WriteLine(); writer.WriteLine("array->ref -= 1;"); writer.WriteLine(); writer.WriteLine("if (array->ref == 0)"); writer.WriteLine("{"); using (writer.Indent()) { writer.WriteLine("for (size_t i = 0; i < array->count; ++i)"); writer.WriteLine("{"); using (writer.Indent()) { EmitCopyDestructor("array->items[i]", arrayType.ElementType); } writer.WriteLine("}"); writer.WriteLine(); writer.WriteLine("free(array);"); } writer.WriteLine("}"); } writer.WriteLine("}"); writer.WriteLine(); writer.WriteLine($"static inline {name} {name}_make()"); writer.WriteLine("{"); using (writer.Indent()) { writer.WriteLine($"{name} array = ({name})malloc(sizeof({backingName}));"); writer.WriteLine("array->ref = 1;"); writer.WriteLine("array->count = 0;"); writer.WriteLine("array->capacity = 0;"); writer.WriteLine("array->items = NULL;"); writer.WriteLine("return array;"); } writer.WriteLine("}"); writer.WriteLine(); break; } } } private void EmitStatement(TypedNodeStatement node) { if (scopes.Peek().Unreachable) return; switch (node) { case TypedNodeStatementBlock statement: EmitStatementBlock(statement); break; case TypedNodeStatementFuncCall statement: EmitStatementFuncCall(statement); break; case TypedNodeStatementReturn statement: EmitStatementReturn(statement); break; case TypedNodeStatementVariableDeclaration statement: EmitStatementVariableDeclaration(statement); break; case TypedNodeStatementAssignment statement: EmitStatementAssignment(statement); break; case TypedNodeStatementIf statement: EmitStatementIf(statement); break; case TypedNodeStatementWhile statement: EmitStatementWhile(statement); break; case TypedNodeStatementFor statement: EmitStatementFor(statement); break; case TypedNodeStatementMatch statement: EmitStatementMatch(statement); break; default: throw new ArgumentOutOfRangeException(nameof(node), node, null); } } private void EmitStatementBlock(TypedNodeStatementBlock node) { writer.WriteLine("{"); using (writer.Indent()) { PushScope(); foreach (var statement in node.Statements) EmitStatement(statement); PopScope(); } writer.WriteLine("}"); } private void EmitStatementFuncCall(TypedNodeStatementFuncCall node) { var name = EmitExpression(node.Target); var parameterValues = node.Parameters.Select(EmitExpression).ToList(); writer.WriteLine($"{name}({string.Join(", ", parameterValues)});"); } private void EmitStatementReturn(TypedNodeStatementReturn statement) { if (statement.Value != null) { var value = EmitExpression(statement.Value); EmitCleanupAllScopes(); writer.WriteLine($"return {value};"); } else { EmitCleanupAllScopes(); writer.WriteLine($"return;"); } scopes.Peek().Unreachable = true; } private void EmitStatementVariableDeclaration(TypedNodeStatementVariableDeclaration statement) { var value = EmitExpression(statement.Value); EmitCopyConstructor(value, statement.Value.Type); writer.WriteLine($"{TypeName(statement.Type)} {statement.Name.Ident} = {value};"); scopes.Peek().DeconstructableNames.Add((statement.Name.Ident, statement.Type)); } private void EmitStatementAssignment(TypedNodeStatementAssignment statement) { var target = EmitExpression(statement.Target); EmitCopyDestructor(target, statement.Target.Type); var value = EmitExpression(statement.Value); EmitCopyConstructor(value, statement.Value.Type); writer.WriteLine($"{target} = {value};"); } private void EmitStatementIf(TypedNodeStatementIf statement) { var condition = EmitExpression(statement.Condition); writer.WriteLine($"if ({condition})"); writer.WriteLine("{"); using (writer.Indent()) { PushScope(); EmitStatement(statement.ThenBlock); PopScope(); } writer.WriteLine("}"); if (statement.ElseBlock != null) { writer.Write("else"); if (statement.ElseBlock is TypedNodeStatementIf) writer.Write(" "); else writer.WriteLine(); writer.WriteLine("{"); using (writer.Indent()) { PushScope(); EmitStatement(statement.ElseBlock); PopScope(); } writer.WriteLine("}"); } } private void EmitStatementWhile(TypedNodeStatementWhile statement) { var condition = EmitExpression(statement.Condition); writer.WriteLine($"while ({condition})"); writer.WriteLine("{"); using (writer.Indent()) { PushScope(); EmitStatement(statement.Body); PopScope(); } writer.WriteLine("}"); } private void EmitStatementFor(TypedNodeStatementFor statement) { var index = Tmp(); var array = EmitExpression(statement.Array); writer.WriteLine($"for (size_t {index} = 0; {index} < {array}->count; ++{index})"); writer.WriteLine("{"); using (writer.Indent()) { var arrayType = (NubTypeArray)statement.Array.Type; writer.WriteLine($"{TypeName(arrayType.ElementType)} {statement.VariableName.Ident} = {array}->items[{index}];"); EmitStatement(statement.Body); } writer.WriteLine("}"); } private void EmitStatementMatch(TypedNodeStatementMatch statement) { var target = EmitExpression(statement.Target); var enumType = (NubTypeEnum)statement.Target.Type; if (!moduleGraph.TryResolveType(enumType.Module, enumType.Name, true, out var info)) throw new UnreachableException(); var enumInfo = (Module.TypeInfoEnum)info; writer.WriteLine($"switch ({target}.tag)"); writer.WriteLine("{"); using (writer.Indent()) { foreach (var @case in statement.Cases) { var variantInfo = enumInfo.Variants.First(x => x.Name == @case.Variant.Ident); var tag = enumInfo.Variants.ToList().FindIndex(x => x.Name == @case.Variant.Ident); writer.WriteLine($"case {tag}:"); writer.WriteLine("{"); using (writer.Indent()) { PushScope(); if (@case.VariableName != null) { Debug.Assert(variantInfo.Type is not null); writer.WriteLine($"{TypeName(variantInfo.Type)} {@case.VariableName.Ident} = {target}.{@case.Variant.Ident};"); } EmitStatement(@case.Body); PopScope(); writer.WriteLine("break;"); } writer.WriteLine("}"); } } writer.WriteLine("}"); } private string EmitExpression(TypedNodeExpression node) { return node switch { TypedNodeExpressionBinary expression => EmitExpressionBinary(expression), TypedNodeExpressionUnary expression => EmitExpressionUnary(expression), TypedNodeExpressionBoolLiteral expression => expression.Value.Value ? "true" : "false", TypedNodeExpressionIntLiteral expression => expression.Value.Value.ToString(), TypedNodeExpressionStringLiteral expression => EmitExpressionStringLiteral(expression), TypedNodeExpressionStructLiteral expression => EmitExpressionStructLiteral(expression), TypedNodeExpressionEnumLiteral expression => EmitExpressionEnumLiteral(expression), TypedNodeExpressionArrayLiteral expression => EmitNodeExpressionArrayLiteral(expression), TypedNodeExpressionStringConstructor expression => EmitExpressionStringConstructor(expression), TypedNodeExpressionStructMemberAccess expression => EmitExpressionMemberAccess(expression), TypedNodeExpressionStringLength expression => EmitExpressionStringLength(expression), TypedNodeExpressionStringPointer expression => EmitExpressionStringPointer(expression), TypedNodeExpressionArrayCount expression => EmitExpressionArrayCount(expression), TypedNodeExpressionArrayPointer expression => EmitExpressionArrayPointer(expression), TypedNodeExpressionLocalIdent expression => expression.Name, TypedNodeExpressionGlobalIdent expression => EmitNodeExpressionGlobalIdent(expression), TypedNodeExpressionFuncCall expression => EmitExpressionFuncCall(expression), _ => throw new ArgumentOutOfRangeException(nameof(node), node, null) }; } private string EmitExpressionBinary(TypedNodeExpressionBinary expression) { var left = EmitExpression(expression.Left); var right = EmitExpression(expression.Right); var name = Tmp(); if (expression.Operation == TypedNodeExpressionBinary.Op.Add && expression.Left.Type is NubTypeString && expression.Right.Type is NubTypeString) { scopes.Peek().DeconstructableNames.Add((name, expression.Type)); writer.WriteLine($"{TypeName(NubTypeString.Instance)} {name} = string_concat({left}, {right});"); return name; } var op = expression.Operation switch { TypedNodeExpressionBinary.Op.Add => $"({left} + {right})", TypedNodeExpressionBinary.Op.Subtract => $"({left} - {right})", TypedNodeExpressionBinary.Op.Multiply => $"({left} * {right})", TypedNodeExpressionBinary.Op.Divide => $"({left} / {right})", TypedNodeExpressionBinary.Op.Modulo => $"({left} % {right})", TypedNodeExpressionBinary.Op.Equal => $"({left} == {right})", TypedNodeExpressionBinary.Op.NotEqual => $"({left} != {right})", TypedNodeExpressionBinary.Op.LessThan => $"({left} < {right})", TypedNodeExpressionBinary.Op.LessThanOrEqual => $"({left} <= {right})", TypedNodeExpressionBinary.Op.GreaterThan => $"({left} > {right})", TypedNodeExpressionBinary.Op.GreaterThanOrEqual => $"({left} >= {right})", TypedNodeExpressionBinary.Op.LeftShift => $"({left} << {right})", TypedNodeExpressionBinary.Op.RightShift => $"({left} >> {right})", TypedNodeExpressionBinary.Op.LogicalAnd => $"({left} && {right})", TypedNodeExpressionBinary.Op.LogicalOr => $"({left} || {right})", _ => throw new ArgumentOutOfRangeException() }; writer.WriteLine($"{TypeName(expression.Type)} {name} = {op};"); return name; } private string EmitExpressionUnary(TypedNodeExpressionUnary expression) { var target = EmitExpression(expression.Target); var name = Tmp(); var op = expression.Operation switch { TypedNodeExpressionUnary.Op.Negate => $"(-{target})", TypedNodeExpressionUnary.Op.Invert => $"(!{target})", _ => throw new ArgumentOutOfRangeException() }; writer.WriteLine($"{TypeName(expression.Type)} {name} = {op};"); return name; } private string EmitExpressionStringLiteral(TypedNodeExpressionStringLiteral expression) { var name = Tmp(); referencedStringLiterals.Add(name, expression.Value.Value); return $"(&{name})"; } private string EmitExpressionStructLiteral(TypedNodeExpressionStructLiteral expression) { var name = Tmp(); scopes.Peek().DeconstructableNames.Add((name, expression.Type)); var initializerValues = new Dictionary(); foreach (var initializer in expression.Initializers) { var value = EmitExpression(initializer.Value); EmitCopyConstructor(value, initializer.Value.Type); initializerValues[initializer.Name.Ident] = value; } var initializerStrings = initializerValues.Select(x => $".{x.Key} = {x.Value}"); writer.WriteLine($"{TypeName(expression.Type)} {name} = ({TypeName(expression.Type)}){{ {string.Join(", ", initializerStrings)} }};"); return name; } private string EmitExpressionEnumLiteral(TypedNodeExpressionEnumLiteral expression) { var name = Tmp(); scopes.Peek().DeconstructableNames.Add((name, expression.Type)); var enumVariantType = (NubTypeEnumVariant)expression.Type; if (!moduleGraph.TryResolveType(enumVariantType.EnumType.Module, enumVariantType.EnumType.Name, true, out var info)) throw new UnreachableException(); var enumInfo = (Module.TypeInfoEnum)info; var tag = enumInfo.Variants.ToList().FindIndex(x => x.Name == enumVariantType.Variant); string? value = null; if (expression.Value != null) { value = EmitExpression(expression.Value); EmitCopyConstructor(value, expression.Value.Type); } writer.Write($"{TypeName(expression.Type)} {name} = ({TypeName(expression.Type)}){{ .tag = {tag}"); if (value != null) writer.WriteLine($", .{enumVariantType.Variant} = {value} }};"); else writer.WriteLine(" };"); return name; } private string EmitNodeExpressionArrayLiteral(TypedNodeExpressionArrayLiteral expression) { var name = Tmp(); scopes.Peek().DeconstructableNames.Add((name, expression.Type)); writer.WriteLine($"{TypeName(expression.Type)} {name} = {TypeName(expression.Type)}_make();"); foreach (var value in expression.Values) { var valueName = EmitExpression(value); writer.WriteLine($"da_append({name}, {valueName});"); } return name; } private string EmitExpressionStringConstructor(TypedNodeExpressionStringConstructor expression) { var name = Tmp(); scopes.Peek().DeconstructableNames.Add((name, expression.Type)); var value = EmitExpression(expression.Value); writer.WriteLine($"{TypeName(expression.Type)} {name} = string_from_cstr({value});"); return name; } private string EmitExpressionMemberAccess(TypedNodeExpressionStructMemberAccess expression) { var target = EmitExpression(expression.Target); return $"{target}.{expression.Name.Ident}"; } private string EmitExpressionStringLength(TypedNodeExpressionStringLength expression) { var target = EmitExpression(expression.Target); return $"{target}->length"; } private string EmitExpressionStringPointer(TypedNodeExpressionStringPointer expression) { var target = EmitExpression(expression.Target); return $"{target}->data"; } private string EmitExpressionArrayCount(TypedNodeExpressionArrayCount expression) { var target = EmitExpression(expression.Target); return $"{target}->count"; } private string EmitExpressionArrayPointer(TypedNodeExpressionArrayPointer expression) { var target = EmitExpression(expression.Target); return $"{target}->items"; } private string EmitNodeExpressionGlobalIdent(TypedNodeExpressionGlobalIdent expression) { if (!moduleGraph.TryResolveIdentifier(expression.Module, expression.Name, true, out var info)) throw new UnreachableException($"Module graph does not have info about identifier {expression.Module}::{expression.Name}. This should have been caught earlier"); return info.MangledName; } private string EmitExpressionFuncCall(TypedNodeExpressionFuncCall expression) { var name = EmitExpression(expression.Target); var parameterValues = expression.Parameters.Select(EmitExpression).ToList(); var tmp = Tmp(); writer.WriteLine($"{TypeName(expression.Type)} {tmp} = {name}({string.Join(", ", parameterValues)});"); return tmp; } private readonly Dictionary typeNames = []; private string TypeName(NubType type) { if (!typeNames.TryGetValue(type, out var name)) { name = type switch { NubTypeVoid => "void", NubTypeBool => "bool", NubTypeStruct => Tmp(), NubTypeAnonymousStruct => Tmp(), NubTypeEnum => Tmp(), NubTypeEnumVariant t => TypeName(t.EnumType), NubTypeSInt t => $"int{t.Width}_t", NubTypeUInt t => $"uint{t.Width}_t", NubTypePointer => Tmp(), NubTypeString => "string_ptr", NubTypeFunc => Tmp(), NubTypeArray => Tmp(), _ => throw new NotImplementedException(), }; typeNames[type] = name; } return name; } private string Tmp() { return $"_tmp{tmpNameIndex++}"; } private void EmitCleanupAllScopes() { foreach (var scope in scopes.Reverse()) { for (int i = scope.DeconstructableNames.Count - 1; i >= 0; i--) { var (name, type) = scope.DeconstructableNames[i]; EmitCopyDestructor(name, type); } } } private void EmitCleanupCurrentScope(Scope scope) { for (int i = scope.DeconstructableNames.Count - 1; i >= 0; i--) { var (name, type) = scope.DeconstructableNames[i]; EmitCopyDestructor(name, type); } } private void EmitCopyConstructor(string value, NubType type) { switch (type) { case NubTypeArray arrayType: { writer.WriteLine($"{TypeName(type)}_rc_inc({value});"); break; } case NubTypeString: { writer.WriteLine($"string_rc_inc({value});"); break; } case NubTypeStruct structType: { if (!moduleGraph.TryResolveType(structType.Module, structType.Name, true, out var info) || info is not Module.TypeInfoStruct structInfo) throw new UnreachableException(); foreach (var field in structInfo.Fields) { EmitCopyConstructor($"{value}.{field.Name}", field.Type); } break; } case NubTypeAnonymousStruct anonymousStructType: { foreach (var field in anonymousStructType.Fields) { EmitCopyConstructor($"{value}.{field.Name}", field.Type); } break; } case NubTypeEnum enumType: { if (!moduleGraph.TryResolveType(enumType.Module, enumType.Name, true, out var info) || info is not Module.TypeInfoEnum enumInfo) throw new UnreachableException(); writer.WriteLine($"switch ({value}.tag)"); writer.WriteLine("{"); using (writer.Indent()) { for (int i = 0; i < enumInfo.Variants.Count; i++) { Module.TypeInfoEnum.Variant variant = enumInfo.Variants[i]; if (variant.Type is not null) { writer.WriteLine($"case {i}:"); writer.WriteLine("{"); using (writer.Indent()) { EmitCopyConstructor($"{value}.{variant.Name}", variant.Type); writer.WriteLine("break;"); } writer.WriteLine("}"); } } } writer.WriteLine("}"); break; } case NubTypeEnumVariant enumVariantType: { if (!moduleGraph.TryResolveType(enumVariantType.EnumType.Module, enumVariantType.EnumType.Name, true, out var info) || info is not Module.TypeInfoEnum enumInfo) throw new UnreachableException(); var variant = enumInfo.Variants.First(x => x.Name == enumVariantType.Variant); if (variant.Type is not null) EmitCopyConstructor($"{value}.{variant.Name}", variant.Type); break; } } } private void EmitCopyDestructor(string value, NubType type) { switch (type) { case NubTypeArray arrayType: { writer.WriteLine($"{TypeName(type)}_rc_dec({value});"); break; } case NubTypeString: { writer.WriteLine($"string_rc_dec({value});"); break; } case NubTypeStruct structType: { if (!moduleGraph.TryResolveType(structType.Module, structType.Name, true, out var info) || info is not Module.TypeInfoStruct structInfo) throw new UnreachableException(); foreach (var field in structInfo.Fields) { EmitCopyDestructor($"{value}.{field.Name}", field.Type); } break; } case NubTypeAnonymousStruct anonymousStructType: { foreach (var field in anonymousStructType.Fields) { EmitCopyDestructor($"{value}.{field.Name}", field.Type); } break; } case NubTypeEnum enumType: { if (!moduleGraph.TryResolveType(enumType.Module, enumType.Name, true, out var info) || info is not Module.TypeInfoEnum enumInfo) throw new UnreachableException(); writer.WriteLine($"switch ({value}.tag)"); writer.WriteLine("{"); using (writer.Indent()) { for (int i = 0; i < enumInfo.Variants.Count; i++) { var variant = enumInfo.Variants[i]; if (variant.Type is not null) { writer.WriteLine($"case {i}:"); writer.WriteLine("{"); using (writer.Indent()) { EmitCopyDestructor($"{value}.{variant.Name}", variant.Type); writer.WriteLine("break;"); } writer.WriteLine("}"); } } } writer.WriteLine("}"); break; } case NubTypeEnumVariant enumVariantType: { if (!moduleGraph.TryResolveType(enumVariantType.EnumType.Module, enumVariantType.EnumType.Name, true, out var info) || info is not Module.TypeInfoEnum enumInfo) throw new UnreachableException(); var variant = enumInfo.Variants.First(x => x.Name == enumVariantType.Variant); if (variant.Type is not null) EmitCopyDestructor($"{value}.{variant.Name}", variant.Type); break; } } } private void PushScope() { scopes.Push(new Scope()); } private void PopScope() { var scope = scopes.Pop(); if (!scope.Unreachable) EmitCleanupCurrentScope(scope); } private class Scope { public List<(string Name, NubType Type)> DeconstructableNames { get; } = []; public bool Unreachable { get; set; } } } internal class IndentedTextWriter { private readonly StringBuilder builder = new(); private int indentLevel; public IDisposable Indent() { indentLevel++; return new IndentScope(this); } public void WriteLine(string text) { WriteIndent(); builder.AppendLine(text); } public void Write(string text) { WriteIndent(); builder.Append(text); } public void WriteLine() { builder.AppendLine(); } public override string ToString() { return builder.ToString(); } private void WriteIndent() { if (builder.Length > 0) { var lastChar = builder[^1]; if (lastChar != '\n' && lastChar != '\r') return; } for (var i = 0; i < indentLevel; i++) { builder.Append(" "); } } private class IndentScope(IndentedTextWriter writer) : IDisposable { private bool disposed; public void Dispose() { if (disposed) return; writer.indentLevel--; disposed = true; } } }