using System.Diagnostics; using System.Text; namespace Compiler; public class Generator { public static string Emit(List functions, ModuleGraph moduleGraph, bool compileLib) { return new Generator(functions, moduleGraph, compileLib).Emit(); } private Generator(List functions, ModuleGraph moduleGraph, bool compileLib) { this.functions = functions; this.moduleGraph = moduleGraph; this.compileLib = compileLib; } private readonly List functions; private readonly ModuleGraph moduleGraph; private readonly bool compileLib; private readonly IndentedTextWriter writer = new(); private string Emit() { writer.WriteLine(""" #include #include #include #include #include struct nub_core_string { const char *data; int length; }; """); writer.WriteLine(); foreach (var module in moduleGraph.GetModules()) { foreach (var (name, info) in module.GetTypes()) { switch (info) { case Module.TypeInfoStruct s: writer.WriteLine($"struct {NameMangler.Mangle(module.Name, name, NubTypeStruct.Get(module.Name, name))};"); break; case Module.TypeInfoEnum e: writer.WriteLine($"struct {NameMangler.Mangle(module.Name, name, NubTypeStruct.Get(module.Name, name))};"); break; } } } foreach (var module in moduleGraph.GetModules()) { foreach (var (name, info) in module.GetTypes()) { switch (info) { case Module.TypeInfoStruct s: { writer.WriteLine(); writer.Write("struct "); if (s.Packed) writer.Write("__attribute__((__packed__)) "); writer.WriteLine(NameMangler.Mangle(module.Name, name, NubTypeStruct.Get(module.Name, name))); writer.WriteLine("{"); using (writer.Indent()) { foreach (var field in s.Fields) { writer.WriteLine($"{CType(field.Type, field.Name)};"); } } writer.WriteLine("};"); break; } case Module.TypeInfoEnum e: { writer.WriteLine(); writer.Write($"struct {NameMangler.Mangle(module.Name, name, NubTypeEnum.Get(module.Name, name))}"); writer.WriteLine("{"); using (writer.Indent()) { writer.WriteLine("uint32_t tag;"); writer.WriteLine("union"); writer.WriteLine("{"); using (writer.Indent()) { foreach (var variant in e.Variants) { writer.WriteLine("struct"); writer.WriteLine("{"); using (writer.Indent()) { foreach (var field in variant.Fields) { writer.WriteLine($"{CType(field.Type, field.Name)};"); } } writer.WriteLine($"}} {variant.Name};"); } } writer.WriteLine("};"); } writer.WriteLine("};"); break; } } } } writer.WriteLine(); foreach (var module in moduleGraph.GetModules()) { foreach (var (name, info) in module.GetIdentifiers()) { if (info.Type is NubTypeFunc fn) { switch (info.Kind) { case Module.DefinitionKind.External: writer.Write("extern "); break; case Module.DefinitionKind.Internal: writer.Write("static "); break; } writer.WriteLine($"{CType(fn.ReturnType, info.MangledName)}({string.Join(", ", fn.Parameters.Select(p => CType(p)))});"); } else { writer.WriteLine($"{CType(info.Type, info.MangledName)};"); } } } writer.WriteLine(); if (!compileLib) { if (!moduleGraph.TryResolveIdentifier("main", "main", true, out var info)) throw new UnreachableException("func main::main() is not defined. This should have been caught earlier"); writer.WriteLine($$""" int main(int argc, char *argv[]) { return {{info.MangledName}}(); } """); 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.Kind == Module.DefinitionKind.Internal) writer.Write("static "); var parameters = function.Parameters.Select(x => CType(x.Type, x.Name.Ident)); writer.WriteLine($"{CType(function.ReturnType, info.MangledName)}({string.Join(", ", parameters)})"); writer.WriteLine("{"); using (writer.Indent()) { EmitStatement(function.Body); } writer.WriteLine("}"); writer.WriteLine(); } return writer.ToString(); } private void EmitStatement(TypedNodeStatement node) { 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 TypedNodeStatementMatch statement: EmitStatementMatch(statement); break; default: throw new ArgumentOutOfRangeException(nameof(node), node, null); } } private void EmitStatementBlock(TypedNodeStatementBlock node) { writer.WriteLine("{"); using (writer.Indent()) { foreach (var statement in node.Statements) EmitStatement(statement); } 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) { var value = EmitExpression(statement.Value); writer.WriteLine($"return {value};"); } private void EmitStatementVariableDeclaration(TypedNodeStatementVariableDeclaration statement) { var value = EmitExpression(statement.Value); writer.WriteLine($"{CType(statement.Type)} {statement.Name.Ident} = {value};"); } private void EmitStatementAssignment(TypedNodeStatementAssignment statement) { var target = EmitExpression(statement.Target); var value = EmitExpression(statement.Value); writer.WriteLine($"{target} = {value};"); } private void EmitStatementIf(TypedNodeStatementIf statement) { var condition = EmitExpression(statement.Condition); writer.WriteLine($"if ({condition})"); writer.WriteLine("{"); using (writer.Indent()) { EmitStatement(statement.ThenBlock); } writer.WriteLine("}"); if (statement.ElseBlock != null) { writer.Write("else"); if (statement.ElseBlock is TypedNodeStatementIf) writer.Write(" "); else writer.WriteLine(); writer.WriteLine("{"); using (writer.Indent()) { EmitStatement(statement.ElseBlock); } writer.WriteLine("}"); } } private void EmitStatementWhile(TypedNodeStatementWhile statement) { var condition = EmitExpression(statement.Condition); writer.WriteLine($"while ({condition})"); writer.WriteLine("{"); using (writer.Indent()) { 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 tag = enumInfo.Variants.ToList().FindIndex(x => x.Name == @case.Type.Ident); writer.WriteLine($"case {tag}:"); writer.WriteLine("{"); using (writer.Indent()) { writer.WriteLine($"auto {@case.VariableName.Ident} = {target}.{@case.Type.Ident};"); EmitStatement(@case.Body); } 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 => $"(struct nub_core_string){{ \"{expression.Value.Value}\", {expression.Value.Value.Length} }}", TypedNodeExpressionStructLiteral expression => EmitExpressionStructLiteral(expression), TypedNodeExpressionEnumLiteral expression => EmitExpressionEnumLiteral(expression), TypedNodeExpressionMemberAccess expression => EmitExpressionMemberAccess(expression), TypedNodeExpressionLocalIdent expression => expression.Value.Ident, TypedNodeExpressionModuleIdent expression => EmitExpressionModuleIdent(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); return 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() }; } private string EmitExpressionUnary(TypedNodeExpressionUnary expression) { var target = EmitExpression(expression.Target); return expression.Operation switch { TypedNodeExpressionUnary.Op.Negate => $"(-{target})", TypedNodeExpressionUnary.Op.Invert => $"(!{target})", _ => throw new ArgumentOutOfRangeException() }; } private string EmitExpressionStructLiteral(TypedNodeExpressionStructLiteral expression) { var initializerValues = new Dictionary(); foreach (var initializer in expression.Initializers) { var values = EmitExpression(initializer.Value); initializerValues[initializer.Name.Ident] = values; } var initializerStrings = initializerValues.Select(x => $".{x.Key} = {x.Value}"); return $"({CType(expression.Type)}){{ {string.Join(", ", initializerStrings)} }}"; } private string EmitExpressionEnumLiteral(TypedNodeExpressionEnumLiteral expression) { 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); var initializerValues = new Dictionary(); foreach (var initializer in expression.Initializers) { var values = EmitExpression(initializer.Value); initializerValues[initializer.Name.Ident] = values; } var initializerStrings = initializerValues.Select(x => $".{x.Key} = {x.Value}"); return $"({CType(expression.Type)}){{ .tag = {tag}, .{enumVariantType.Variant} = {{ {string.Join(", ", initializerStrings)} }} }}"; } private string EmitExpressionMemberAccess(TypedNodeExpressionMemberAccess expression) { var target = EmitExpression(expression.Target); return $"{target}.{expression.Name.Ident}"; } private string EmitExpressionModuleIdent(TypedNodeExpressionModuleIdent expression) { if (!moduleGraph.TryResolveIdentifier(expression.Module.Ident, expression.Value.Ident, true, out var info)) throw new UnreachableException($"Module graph does not have info about identifier {expression.Module.Ident}::{expression.Value.Ident}. 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(); return $"{name}({string.Join(", ", parameterValues)})"; } private string CType(NubType node, string? varName = null) { return node switch { NubTypeVoid => "void" + (varName != null ? $" {varName}" : ""), NubTypeBool => "bool" + (varName != null ? $" {varName}" : ""), NubTypeStruct type => $"struct {NameMangler.Mangle(type.Module, type.Name, type)}" + (varName != null ? $" {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}" : ""), NubTypeUInt type => $"uint{type.Width}_t" + (varName != null ? $" {varName}" : ""), NubTypePointer type => CType(type.To) + (varName != null ? $" *{varName}" : "*"), NubTypeString => "struct nub_core_string" + (varName != null ? $" {varName}" : ""), NubTypeFunc type => $"{CType(type.ReturnType)} (*{varName})({string.Join(", ", type.Parameters.Select(p => CType(p)))})", _ => throw new ArgumentOutOfRangeException(nameof(node), node, null) }; } } 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; } } }