using NubLang.Ast; using NubLang.Syntax; namespace NubLang.Generation.C; public class Generator { private readonly CompilationUnit _compilationUnit; private readonly IndentedTextWriter _writer; private readonly Stack> _deferStack = []; private int _tmpIndex; public Generator(CompilationUnit compilationUnit) { _compilationUnit = compilationUnit; _writer = new IndentedTextWriter(); } // todo(nub31): Handle name collisions private string NewTmp() { return $"_t{++_tmpIndex}"; } private static string FuncName(string module, string name, string? externSymbol) { return externSymbol ?? $"{module}_{name}"; } private static string StructName(string module, string name) { return $"{module}_{name}"; } public string Emit() { _writer.WriteLine("#include "); _writer.WriteLine("#include "); _writer.WriteLine("#include "); _writer.WriteLine(); foreach (var structType in _compilationUnit.ImportedStructTypes) { _writer.WriteLine("typedef struct"); _writer.WriteLine("{"); using (_writer.Indent()) { foreach (var field in structType.Fields) { _writer.WriteLine($"{CType.Create(field.Type, field.Name)};"); } } _writer.WriteLine($"}} {StructName(structType.Module, structType.Name)};"); _writer.WriteLine(); } // note(nub31): Forward declarations foreach (var prototype in _compilationUnit.ImportedFunctions) { EmitLine(prototype.Tokens.FirstOrDefault()); var parameters = prototype.Parameters.Count != 0 ? string.Join(", ", prototype.Parameters.Select(x => CType.Create(x.Type, x.Name))) : "void"; var name = FuncName(prototype.Module, prototype.Name, prototype.ExternSymbol); _writer.WriteLine($"{CType.Create(prototype.ReturnType, name)}({parameters});"); _writer.WriteLine(); } // note(nub31): declare extern functions foreach (var funcNode in _compilationUnit.Functions) { if (funcNode.Body != null) continue; EmitLine(funcNode.Tokens.FirstOrDefault()); var parameters = funcNode.Prototype.Parameters.Count != 0 ? string.Join(", ", funcNode.Prototype.Parameters.Select(x => CType.Create(x.Type, x.Name))) : "void"; var name = FuncName(funcNode.Module, funcNode.Name, funcNode.Prototype.ExternSymbol); _writer.WriteLine($"{CType.Create(funcNode.Prototype.ReturnType, name)}({parameters});"); } _writer.WriteLine(); // note(nub31): Normal functions foreach (var funcNode in _compilationUnit.Functions) { if (funcNode.Body == null) continue; EmitLine(funcNode.Tokens.FirstOrDefault()); var parameters = funcNode.Prototype.Parameters.Count != 0 ? string.Join(", ", funcNode.Prototype.Parameters.Select(x => CType.Create(x.Type, x.Name))) : "void"; if (funcNode.Prototype.ExternSymbol == null) { _writer.Write("static "); } var name = FuncName(funcNode.Module, funcNode.Name, funcNode.Prototype.ExternSymbol); _writer.WriteLine($"{CType.Create(funcNode.Prototype.ReturnType, name)}({parameters})"); EmitBlock(funcNode.Body); _writer.WriteLine(); } return _writer.ToString(); } private void EmitStatement(StatementNode statementNode) { EmitLine(statementNode.Tokens.FirstOrDefault()); switch (statementNode) { case AssignmentNode assignmentNode: EmitAssignment(assignmentNode); break; case BlockNode blockNode: EmitBlock(blockNode); break; case BreakNode breakNode: EmitBreak(breakNode); break; case ContinueNode continueNode: EmitContinue(continueNode); break; case DeferNode deferNode: EmitDefer(deferNode); break; case IfNode ifNode: EmitIf(ifNode); break; case ReturnNode returnNode: EmitReturn(returnNode); break; case StatementFuncCallNode statementFuncCallNode: EmitStatementFuncCall(statementFuncCallNode); break; case VariableDeclarationNode variableDeclarationNode: EmitVariableDeclaration(variableDeclarationNode); break; case WhileNode whileNode: EmitWhile(whileNode); break; default: throw new ArgumentOutOfRangeException(nameof(statementNode)); } } private void EmitLine(Token? token) { if (token == null) return; var file = token.Span.FilePath; var line = token.Span.Start.Line; _writer.WriteLine($"#line {line} \"{file}\""); } private void EmitAssignment(AssignmentNode assignmentNode) { var target = EmitExpression(assignmentNode.Target); var value = EmitExpression(assignmentNode.Value); _writer.WriteLine($"{target} = {value};"); } private void EmitBreak(BreakNode _) { _writer.WriteLine("break;"); } private void EmitContinue(ContinueNode _) { _writer.WriteLine("continue;"); } private void EmitDefer(DeferNode deferNode) { _deferStack.Peek().Add(deferNode); } private void EmitIf(IfNode ifNode, bool elseIf = false) { var condition = EmitExpression(ifNode.Condition); _writer.WriteLine($"{(elseIf ? "else " : "")}if ({condition})"); EmitBlock(ifNode.Body); ifNode.Else?.Match ( elseIfNode => EmitIf(elseIfNode, true), elseNode => { _writer.WriteLine("else"); EmitBlock(elseNode); } ); } private void EmitReturn(ReturnNode returnNode) { if (returnNode.Value == null) { var blockDefers = _deferStack.Peek(); for (var i = blockDefers.Count - 1; i >= 0; i--) { EmitStatement(blockDefers[i].Statement); } _writer.WriteLine("return;"); } else { var returnValue = EmitExpression(returnNode.Value); if (_deferStack.Peek().Count != 0) { var tmp = NewTmp(); _writer.WriteLine($"{CType.Create(returnNode.Value.Type, tmp)} = {returnValue};"); var blockDefers = _deferStack.Peek(); for (var i = blockDefers.Count - 1; i >= 0; i--) { EmitStatement(blockDefers[i].Statement); } EmitLine(returnNode.Tokens.FirstOrDefault()); _writer.WriteLine($"return {tmp};"); } else { EmitLine(returnNode.Tokens.FirstOrDefault()); _writer.WriteLine($"return {returnValue};"); } } } private void EmitStatementFuncCall(StatementFuncCallNode statementFuncCallNode) { var funcCall = EmitFuncCall(statementFuncCallNode.FuncCall); _writer.WriteLine($"{funcCall};"); } private void EmitVariableDeclaration(VariableDeclarationNode variableDeclarationNode) { if (variableDeclarationNode.Assignment != null) { var value = EmitExpression(variableDeclarationNode.Assignment); _writer.WriteLine($"{CType.Create(variableDeclarationNode.Type, variableDeclarationNode.Name)} = {value};"); } else { _writer.WriteLine($"{CType.Create(variableDeclarationNode.Type, variableDeclarationNode.Name)};"); } } private void EmitWhile(WhileNode whileNode) { var condition = EmitExpression(whileNode.Condition); _writer.WriteLine($"while ({condition})"); EmitBlock(whileNode.Body); } private string EmitExpression(ExpressionNode expressionNode) { var expr = expressionNode switch { ArrayIndexAccessNode arrayIndexAccessNode => EmitArrayIndexAccess(arrayIndexAccessNode), ArrayInitializerNode arrayInitializerNode => EmitArrayInitializer(arrayInitializerNode), BinaryExpressionNode binaryExpressionNode => EmitBinaryExpression(binaryExpressionNode), BoolLiteralNode boolLiteralNode => EmitBoolLiteral(boolLiteralNode), ConstArrayIndexAccessNode constArrayIndexAccessNode => EmitConstArrayIndexAccess(constArrayIndexAccessNode), ConstArrayInitializerNode constArrayInitializerNode => EmitConstArrayInitializer(constArrayInitializerNode), // ConvertConstArrayToArrayNode convertConstArrayToArrayNode => EmitConvertConstArrayToArray(convertConstArrayToArrayNode), ConvertFloatNode convertFloatNode => EmitConvertFloat(convertFloatNode), ConvertIntNode convertIntNode => EmitConvertInt(convertIntNode), CStringLiteralNode cStringLiteralNode => EmitCStringLiteral(cStringLiteralNode), DereferenceNode dereferenceNode => EmitDereference(dereferenceNode), Float32LiteralNode float32LiteralNode => EmitFloat32Literal(float32LiteralNode), Float64LiteralNode float64LiteralNode => EmitFloat64Literal(float64LiteralNode), FloatToIntBuiltinNode floatToIntBuiltinNode => EmitFloatToIntBuiltin(floatToIntBuiltinNode), FuncCallNode funcCallNode => EmitFuncCall(funcCallNode), FuncIdentifierNode funcIdentifierNode => EmitFuncIdentifier(funcIdentifierNode), IntLiteralNode intLiteralNode => EmitIntLiteral(intLiteralNode), AddressOfNode addressOfNode => EmitAddressOf(addressOfNode), LValueIdentifierNode lValueIdentifierNode => EmitLValueIdentifier(lValueIdentifierNode), RValueIdentifierNode rValueIdentifierNode => EmitRValueIdentifier(rValueIdentifierNode), SizeBuiltinNode sizeBuiltinNode => EmitSizeBuiltin(sizeBuiltinNode), SliceIndexAccessNode sliceIndexAccessNode => EmitSliceArrayIndexAccess(sliceIndexAccessNode), StringLiteralNode stringLiteralNode => EmitStringLiteral(stringLiteralNode), StructFieldAccessNode structFieldAccessNode => EmitStructFieldAccess(structFieldAccessNode), StructInitializerNode structInitializerNode => EmitStructInitializer(structInitializerNode), UIntLiteralNode uIntLiteralNode => EmitUIntLiteral(uIntLiteralNode), UnaryExpressionNode unaryExpressionNode => EmitUnaryExpression(unaryExpressionNode), _ => throw new ArgumentOutOfRangeException(nameof(expressionNode)) }; if (expressionNode is ConstArrayInitializerNode) { return expr; } return $"({expr})"; } private string EmitArrayIndexAccess(ArrayIndexAccessNode arrayIndexAccessNode) { var array = EmitExpression(arrayIndexAccessNode.Target); var index = EmitExpression(arrayIndexAccessNode.Index); return $"{array}[{index}]"; } private string EmitArrayInitializer(ArrayInitializerNode arrayInitializerNode) { var type = (NubArrayType)arrayInitializerNode.Type; var capacity = EmitExpression(arrayInitializerNode.Capacity); var tmp = NewTmp(); _writer.WriteLine($"{CType.Create(type.ElementType)} {tmp}[{capacity}];"); return tmp; } private string EmitBinaryExpression(BinaryExpressionNode binaryExpressionNode) { var left = EmitExpression(binaryExpressionNode.Left); var right = EmitExpression(binaryExpressionNode.Right); var op = binaryExpressionNode.Operator switch { BinaryOperator.Plus => "+", BinaryOperator.Minus => "-", BinaryOperator.Multiply => "*", BinaryOperator.Divide => "/", BinaryOperator.Modulo => "%", BinaryOperator.Equal => "==", BinaryOperator.NotEqual => "!=", BinaryOperator.LessThan => "<", BinaryOperator.LessThanOrEqual => "<=", BinaryOperator.GreaterThan => ">", BinaryOperator.GreaterThanOrEqual => ">=", BinaryOperator.LogicalAnd => "&&", BinaryOperator.LogicalOr => "||", BinaryOperator.BitwiseAnd => "&", BinaryOperator.BitwiseOr => "|", BinaryOperator.BitwiseXor => "^", BinaryOperator.LeftShift => "<<", BinaryOperator.RightShift => ">>", _ => throw new ArgumentOutOfRangeException() }; return $"{left} {op} {right}"; } private string EmitBoolLiteral(BoolLiteralNode boolLiteralNode) { return boolLiteralNode.Value ? "true" : "false"; } private string EmitConstArrayIndexAccess(ConstArrayIndexAccessNode constArrayIndexAccessNode) { var array = EmitExpression(constArrayIndexAccessNode.Target); var index = EmitExpression(constArrayIndexAccessNode.Index); // todo(nub31): We can emit bounds checking here return $"{array}[{index}]"; } private string EmitConstArrayInitializer(ConstArrayInitializerNode constArrayInitializerNode) { return "{0}"; } // private string EmitConvertConstArrayToArray(ConvertConstArrayToArrayNode convertConstArrayToArrayNode) // { // var value = EmitExpression(convertConstArrayToArrayNode.Value); // return $"({CType.Create(convertConstArrayToArrayNode.Type)}){value}"; // } private string EmitConvertFloat(ConvertFloatNode convertFloatNode) { var value = EmitExpression(convertFloatNode.Value); var targetCast = convertFloatNode.TargetType.Width switch { 32 => "f32", 64 => "f64", _ => throw new ArgumentOutOfRangeException() }; return $"({targetCast}){value}"; } private string EmitConvertInt(ConvertIntNode convertIntNode) { var value = EmitExpression(convertIntNode.Value); var targetType = (convertIntNode.TargetType.Signed, convertIntNode.TargetType.Width) switch { (false, 8) => "u8", (false, 16) => "u16", (false, 32) => "u32", (false, 64) => "u64", (true, 8) => "i8", (true, 16) => "i16", (true, 32) => "i32", (true, 64) => "i64", _ => throw new ArgumentOutOfRangeException() }; return $"({targetType}){value}"; } private string EmitCStringLiteral(CStringLiteralNode cStringLiteralNode) { return $"\"{cStringLiteralNode.Value}\""; } private string EmitDereference(DereferenceNode dereferenceNode) { var pointer = EmitExpression(dereferenceNode.Target); return $"*{pointer}"; } private string EmitFloat32Literal(Float32LiteralNode float32LiteralNode) { var str = float32LiteralNode.Value.ToString("G9", System.Globalization.CultureInfo.InvariantCulture); if (!str.Contains('.') && !str.Contains('e') && !str.Contains('E')) { str += ".0"; } return str + "f"; } private string EmitFloat64Literal(Float64LiteralNode float64LiteralNode) { var str = float64LiteralNode.Value.ToString("G17", System.Globalization.CultureInfo.InvariantCulture); if (!str.Contains('.') && !str.Contains('e') && !str.Contains('E')) { str += ".0"; } return str; } private string EmitFloatToIntBuiltin(FloatToIntBuiltinNode floatToIntBuiltinNode) { var value = EmitExpression(floatToIntBuiltinNode.Value); var targetType = (floatToIntBuiltinNode.TargetType.Signed, floatToIntBuiltinNode.TargetType.Width) switch { (false, 8) => "u8", (false, 16) => "u16", (false, 32) => "u32", (false, 64) => "u64", (true, 8) => "i8", (true, 16) => "i16", (true, 32) => "i32", (true, 64) => "i64", _ => throw new ArgumentOutOfRangeException() }; return $"({targetType}){value}"; } private string EmitFuncCall(FuncCallNode funcCallNode) { var name = EmitExpression(funcCallNode.Expression); var parameterNames = funcCallNode.Parameters.Select(x => EmitExpression(x)).ToList(); return $"{name}({string.Join(", ", parameterNames)})"; } private string EmitFuncIdentifier(FuncIdentifierNode funcIdentifierNode) { return FuncName(funcIdentifierNode.Module, funcIdentifierNode.Name, funcIdentifierNode.ExternSymbol); } private string EmitIntLiteral(IntLiteralNode intLiteralNode) { var type = (NubIntType)intLiteralNode.Type; return type.Width switch { 8 or 16 or 32 => intLiteralNode.Value.ToString(), 64 => intLiteralNode.Value + "LL", _ => throw new ArgumentOutOfRangeException() }; } private string EmitAddressOf(AddressOfNode addressOfNode) { var value = EmitExpression(addressOfNode.LValue); return $"&{value}"; } private string EmitLValueIdentifier(LValueIdentifierNode lValueIdentifierNode) { return lValueIdentifierNode.Name; } private string EmitRValueIdentifier(RValueIdentifierNode rValueIdentifierNode) { return rValueIdentifierNode.Name; } private string EmitSizeBuiltin(SizeBuiltinNode sizeBuiltinNode) { return $"sizeof({CType.Create(sizeBuiltinNode.TargetType)})"; } private string EmitSliceArrayIndexAccess(SliceIndexAccessNode sliceIndexAccessNode) { var value = EmitExpression(sliceIndexAccessNode.Target); var index = EmitExpression(sliceIndexAccessNode.Index); // todo(nub31): We can emit bounds checking here return $"{value}.data[{index}]"; } private string EmitStringLiteral(StringLiteralNode stringLiteralNode) { throw new NotImplementedException(); } private string EmitStructFieldAccess(StructFieldAccessNode structFieldAccessNode) { var structExpr = EmitExpression(structFieldAccessNode.Target); return $"{structExpr}.{structFieldAccessNode.Field}"; } private string EmitStructInitializer(StructInitializerNode structInitializerNode) { var initValues = new List(); foreach (var initializer in structInitializerNode.Initializers) { var value = EmitExpression(initializer.Value); initValues.Add($".{initializer.Key} = {value}"); } var initString = initValues.Count == 0 ? "0" : string.Join(", ", initValues); return $"({CType.Create(structInitializerNode.Type)}){{{initString}}}"; } private string EmitUIntLiteral(UIntLiteralNode uIntLiteralNode) { var type = (NubIntType)uIntLiteralNode.Type; return type.Width switch { 8 or 16 or 32 => uIntLiteralNode.Value + "U", 64 => uIntLiteralNode.Value + "ULL", _ => throw new ArgumentOutOfRangeException() }; } private string EmitUnaryExpression(UnaryExpressionNode unaryExpressionNode) { var value = EmitExpression(unaryExpressionNode.Operand); return unaryExpressionNode.Operator switch { UnaryOperator.Negate => $"-{value}", UnaryOperator.Invert => $"!{value}", _ => throw new ArgumentOutOfRangeException() }; } private void EmitBlock(BlockNode blockNode) { EmitLine(blockNode.Tokens.FirstOrDefault()); _writer.WriteLine("{"); using (_writer.Indent()) { _deferStack.Push([]); foreach (var statementNode in blockNode.Statements) { EmitStatement(statementNode); } var blockDefers = _deferStack.Pop(); for (var i = blockDefers.Count - 1; i >= 0; i--) { EmitStatement(blockDefers[i].Statement); } } EmitLine(blockNode.Tokens.LastOrDefault()); _writer.WriteLine("}"); } }