// GenCs.fu - C# code generator
//
// Copyright (C) 2011-2026 Piotr Fusik
//
// This file is part of Fusion Transpiler,
// see https://github.com/fusionlanguage/fut
//
// Fusion Transpiler is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Fusion Transpiler is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Fusion Transpiler. If not, see http://www.gnu.org/licenses/
public class GenCs : GenTyped
{
bool NewArray;
protected override string GetTargetName() => "C#";
protected override void StartDocLine!()
{
Write("/// ");
}
protected override void WriteDocPara!(FuDocPara para, bool many)
{
if (many) {
WriteNewLine();
Write("/// <para>");
}
foreach (FuDocInline inline in para.Children) {
switch (inline) {
case FuDocText text:
WriteXmlDoc(text.Text);
break;
case FuDocCode code:
switch (code.Text) {
case "true":
case "false":
case "null":
Write("<see langword=\"");
Write(code.Text);
Write("\" />");
break;
default:
Write("<c>");
WriteXmlDoc(code.Text);
Write("</c>");
break;
}
break;
case FuDocLine:
WriteNewLine();
StartDocLine();
break;
default:
assert false;
}
}
if (many)
Write("</para>");
}
protected override void WriteDocList!(FuDocList list)
{
WriteNewLine();
WriteLine("/// <list type=\"bullet\">");
foreach (FuDocPara item in list.Items) {
Write("/// <item>");
WriteDocPara(item, false);
WriteLine("</item>");
}
Write("/// </list>");
}
protected override void WriteDoc!(FuCodeDoc? doc)
{
if (doc == null)
return;
Write("/// <summary>");
WriteDocPara(doc.Summary, false);
WriteLine("</summary>");
if (doc.Details.Count > 0) {
Write("/// <remarks>");
if (doc.Details.Count == 1)
WriteDocBlock(doc.Details[0], false);
else {
foreach (FuDocBlock block in doc.Details)
WriteDocBlock(block, true);
}
WriteLine("</remarks>");
}
}
protected override void WriteName!(FuSymbol symbol)
{
if (symbol is FuConst konst && konst.InMethod != null) {
Write(konst.InMethod.Name);
WriteChar('_');
Write(symbol.Name);
if (konst.InMethodIndex > 0)
VisitLiteralLong(konst.InMethodIndex, FuPriority.Primary);
return;
}
Write(symbol.Name);
switch (symbol.Name) {
case "as":
case "await":
case "catch":
case "char":
case "checked":
case "decimal":
case "delegate":
case "event":
case "explicit":
case "extern":
case "finally":
case "fixed":
case "goto":
case "implicit":
case "interface":
case "is":
case "lock":
case "namespace":
case "object":
case "operator":
case "out":
case "params":
case "private":
case "readonly":
case "ref":
case "sbyte":
case "sizeof":
case "stackalloc":
case "struct":
case "try":
case "typeof":
case "ulong":
case "unchecked":
case "unsafe":
case "using":
case "volatile":
WriteChar('_');
break;
default:
break;
}
}
protected override int GetLiteralChars() => 0x10000;
void WriteVisibility!(FuVisibility visibility)
{
switch (visibility) {
case FuVisibility.Private:
break;
case FuVisibility.Internal:
Write("internal ");
break;
case FuVisibility.Protected:
Write("protected ");
break;
case FuVisibility.Public:
Write("public ");
break;
default:
assert false;
}
}
void WriteCallType!(FuCallType callType, string sealedString)
{
switch (callType) {
case FuCallType.Static:
Write("static ");
break;
case FuCallType.Normal:
break;
case FuCallType.Abstract:
Write("abstract ");
break;
case FuCallType.Virtual:
Write("virtual ");
break;
case FuCallType.Override:
Write("override ");
break;
case FuCallType.Sealed:
Write(sealedString);
break;
}
}
void WriteElementType!(FuType elementType)
{
Include("System.Collections.Generic");
WriteChar('<');
WriteType(elementType, false);
WriteChar('>');
}
protected override void WriteType!(FuType type, bool promote)
{
switch (type) {
case FuIntegerType:
switch (GetTypeId(type, promote)) {
case FuId.SByteRange:
Write("sbyte");
break;
case FuId.ByteRange:
Write("byte");
break;
case FuId.ShortRange:
Write("short");
break;
case FuId.UShortRange:
Write("ushort");
break;
case FuId.IntType:
case FuId.NIntType:
Write("int");
break;
case FuId.LongType:
Write("long");
break;
default:
assert false;
}
break;
case FuClassType klass:
switch (klass.Class.Id) {
case FuId.StringClass:
Write("string");
break;
case FuId.ArrayPtrClass:
case FuId.ArrayStorageClass:
WriteType(klass.GetElementType(), false);
Write("[]");
break;
case FuId.ListClass:
case FuId.QueueClass:
case FuId.StackClass:
case FuId.HashSetClass:
case FuId.SortedSetClass:
Write(klass.Class.Name);
WriteElementType(klass.GetElementType());
break;
case FuId.PriorityQueueClass:
case FuId.DictionaryClass:
case FuId.SortedDictionaryClass:
case FuId.OrderedDictionaryClass:
Include("System.Collections.Generic");
Write(klass.Class.Name);
WriteChar('<');
WriteType(klass.GetKeyType(), false);
Write(", ");
WriteType(klass.GetValueType(), false);
WriteChar('>');
break;
case FuId.TextWriterClass:
case FuId.StringWriterClass:
Include("System.IO");
Write(klass.Class.Name);
break;
case FuId.RegexClass:
case FuId.MatchClass:
Include("System.Text.RegularExpressions");
Write(klass.Class.Name);
break;
case FuId.JsonElementClass:
Include("System.Text.Json");
Write("JsonElement");
break;
case FuId.LockClass:
Write("object");
break;
default:
Write(klass.Class.Name);
break;
}
if (type.Nullable)
WriteChar('?');
break;
default:
Write(type.Name);
break;
}
}
protected override void WriteMemberOp!(FuExpr left, FuSymbolReference? symbol)
{
if (left.Type != null && left.Type.Nullable)
WriteChar('!');
WriteChar('.');
}
protected override void WriteIndexingInfix!(FuExpr collection)
{
if (collection.Type.Nullable)
WriteChar('!');
}
protected override void WriteNewWithFields!(FuReadWriteClassType type, FuAggregateInitializer init)
{
Write("new ");
WriteType(type, false);
WriteObjectLiteral(init, " = ");
}
protected override void WriteCoercedLiteral!(FuType? type, FuExpr expr)
{
if (expr is FuLiteralChar && type is FuRangeType range && range.Max <= 0xff)
WriteStaticCast(type, expr);
else {
base.WriteCoercedLiteral(type, expr);
if (expr.Type != null && expr.Type.Nullable && type != null && !type.Nullable)
WriteChar('!');
}
}
protected override void WriteStaticCast!(FuType type, FuExpr expr)
{
if (expr is FuPrefixExpr unary && unary.Op == FuToken.Tilde && unary.Inner is FuLiteralLong literal &&
(type.Id == FuId.ByteRange || type.Id == FuId.UShortRange))
VisitLiteralLong(literal.Value ^ (type.Id == FuId.ByteRange ? 0xff : 0xffff), FuPriority.Primary);
else {
base.WriteStaticCast(type, expr);
if (expr.Type.Nullable && !type.Nullable)
WriteChar('!');
}
}
protected override void WriteCoercedInternal!(FuType type, FuExpr expr, FuPriority parent)
{
base.WriteCoercedInternal(type, expr, parent);
if (expr.Type.Nullable && !type.Nullable)
WriteChar('!');
}
protected override bool IsPromoted(FuExpr expr) => base.IsPromoted(expr) || expr is FuLiteralChar;
internal override void VisitInterpolatedString!(FuInterpolatedString expr, FuPriority parent)
{
if (expr.IsToString('u')) {
WriteCall("char.ConvertFromUtf32", expr.Parts[0].Argument);
return;
}
Write("$\"");
foreach (FuInterpolatedPart part in expr.Parts) {
WriteDoubling(part.Prefix, '{');
WriteChar('{');
if (part.Format == 'U' || part.Format == 'u')
WriteCall("char.ConvertFromUtf32", part.Argument);
else
part.Argument.Accept(this, FuPriority.SelectCond);
if (part.WidthExpr != null) {
WriteChar(',');
VisitLiteralLong(part.Width, FuPriority.Argument);
}
if (part.Format != ' ' && part.Format != 'U' && part.Format != 'u') {
WriteChar(':');
WriteChar(part.Format);
if (part.Precision >= 0)
VisitLiteralLong(part.Precision, FuPriority.Argument);
}
WriteChar('}');
}
WriteDoubling(expr.Suffix, '{');
WriteChar('"');
}
protected override void WriteNewArray!(FuType elementType, FuExpr lengthExpr, FuPriority parent)
{
if (elementType is FuStorageType) {
this.NewArray = true;
Write("Fu.NewArray(");
lengthExpr.Accept(this, FuPriority.Argument);
Write(", () => ");
WriteNewStorage(elementType);
WriteChar(')');
}
else {
Write("new ");
WriteType(elementType.GetBaseType(), false);
WriteChar('[');
lengthExpr.Accept(this, FuPriority.Argument);
WriteChar(']');
while (elementType is FuClassType array && array.IsArray()) {
Write("[]");
elementType = array.GetElementType();
}
}
}
protected override void WriteNew!(FuReadWriteClassType klass, FuPriority parent)
{
Write("new ");
WriteType(klass, false);
Write("()");
}
protected override bool HasInitCode(FuNamedValue def) => false;
protected override void WriteInitCode!(FuNamedValue def)
{
}
protected override void WriteResource!(string name, int length)
{
Write("FuResource.");
WriteResourceName(name);
}
protected override void WriteStringLength!(FuExpr expr)
{
WritePostfix(expr, ".Length");
}
protected override void WriteArrayLength!(FuExpr expr, FuPriority parent)
{
WritePostfix(expr, ".Length");
}
internal override void VisitSymbolReference!(FuSymbolReference expr, FuPriority parent)
{
switch (expr.Symbol.Id) {
case FuId.ConsoleError:
Include("System");
Write("Console.Error");
break;
case FuId.ConsoleOut:
Include("System");
Write("Console.Out");
break;
case FuId.MatchStart:
WritePostfix(expr.Left, ".Index");
break;
case FuId.MatchEnd:
if (parent > FuPriority.Add)
WriteChar('(');
WritePostfix(expr.Left, ".Index + ");
WriteStringLength(expr.Left); // FIXME: side effect
if (parent > FuPriority.Add)
WriteChar(')');
break;
case FuId.MathNaN:
case FuId.MathNegativeInfinity:
case FuId.MathPositiveInfinity:
Write("float.");
Write(expr.Symbol.Name);
break;
default:
base.VisitSymbolReference(expr, parent);
break;
}
}
void WriteJsonElementIs!(FuExpr obj, string name, FuPriority parent)
{
if (parent > FuPriority.Equality)
WriteChar('(');
WritePostfix(obj, ".ValueKind == JsonValueKind.");
Write(name);
if (parent > FuPriority.Equality)
WriteChar(')');
}
protected override void WriteCallExpr!(FuType type, FuExpr? obj, FuMethod method, List<FuExpr#> args, FuPriority parent)
{
switch (method.Id) {
case FuId.None:
case FuId.ClassToString:
case FuId.EnumHasFlag:
case FuId.StringContains:
case FuId.StringEndsWith:
case FuId.StringReplace:
case FuId.StringStartsWith:
case FuId.StringSubstring:
case FuId.StringToLower:
case FuId.StringToUpper:
case FuId.ListAddRange:
case FuId.ListClear:
case FuId.ListContains:
case FuId.ListCopyTo:
case FuId.ListIndexOf:
case FuId.ListRemoveAt:
case FuId.ListRemoveRange:
case FuId.ListSortAll:
case FuId.QueueClear:
case FuId.QueueDequeue:
case FuId.QueuePeek:
case FuId.StackClear:
case FuId.StackPeek:
case FuId.StackPop:
case FuId.PriorityQueueClear:
case FuId.PriorityQueueDequeue:
case FuId.PriorityQueuePeek:
case FuId.HashSetClear:
case FuId.HashSetContains:
case FuId.HashSetRemove:
case FuId.SortedSetClear:
case FuId.SortedSetContains:
case FuId.SortedSetRemove:
case FuId.DictionaryClear:
case FuId.DictionaryContainsKey:
case FuId.DictionaryRemove:
case FuId.SortedDictionaryClear:
case FuId.SortedDictionaryContainsKey:
case FuId.SortedDictionaryRemove:
case FuId.OrderedDictionaryClear:
case FuId.OrderedDictionaryContainsKey:
case FuId.OrderedDictionaryRemove:
case FuId.TextWriterFlush:
case FuId.ConsoleReadLine:
case FuId.StringWriterToString:
case FuId.BitConverterInt32BitsToSingle:
case FuId.BitConverterInt64BitsToDouble:
case FuId.BitConverterSingleToInt32Bits:
case FuId.BitConverterDoubleToInt64Bits:
case FuId.ConvertToBase64String:
case FuId.JsonElementGetString:
case FuId.JsonElementGetDouble:
case FuId.JsonElementGetBoolean:
if (obj != null) {
obj.Accept(this, FuPriority.Primary);
WriteMemberOp(obj, null);
}
WriteName(method);
WriteCoercedArgsInParentheses(method, args);
break;
case FuId.EnumFromInt:
WriteStaticCast(type, args[0]);
break;
case FuId.EnumToInt:
Write("(int) ");
obj.Accept(this, FuPriority.Primary);
break;
case FuId.IntTryParse:
case FuId.NIntTryParse:
case FuId.LongTryParse:
case FuId.FloatTryParse:
case FuId.DoubleTryParse:
WriteType(obj.Type, false);
Write(".TryParse(");
args[0].Accept(this, FuPriority.Argument);
if (args.Count == 2) {
if (!(args[1] is FuLiteralLong radix) || radix.Value != 16)
NotSupported(args[1], "Radix");
Include("System.Globalization");
Write(", NumberStyles.HexNumber, null");
}
Write(", out ");
obj.Accept(this, FuPriority.Argument);
WriteChar(')');
break;
case FuId.StringIndexOf:
case FuId.StringLastIndexOf:
obj.Accept(this, FuPriority.Primary);
WriteMemberOp(obj, null);
Write(method.Name);
WriteChar('(');
int c = GetOneAscii(args[0]);
if (c >= 0)
VisitLiteralChar(c);
else
args[0].Accept(this, FuPriority.Argument);
WriteChar(')');
break;
case FuId.ArrayBinarySearchAll:
case FuId.ArrayBinarySearchPart:
Include("System");
Write("Array.BinarySearch(");
obj.Accept(this, FuPriority.Argument);
Write(", ");
if (args.Count == 3) {
args[1].Accept(this, FuPriority.Argument);
Write(", ");
args[2].Accept(this, FuPriority.Argument);
Write(", ");
}
WriteNotPromoted(obj.Type.AsClassType().GetElementType(), args[0]);
WriteChar(')');
break;
case FuId.ArrayContains:
Include("System.Linq");
WriteMethodCall(obj, "Contains", args[0]);
break;
case FuId.ArrayCopyTo:
Include("System");
Write("Array.Copy(");
obj.Accept(this, FuPriority.Argument);
Write(", ");
WriteCoercedArgs(method, args);
WriteChar(')');
break;
case FuId.ArrayFillAll:
case FuId.ArrayFillPart:
Include("System");
if (args[0] is FuLiteral literal && literal.IsDefaultValue()) {
Write("Array.Clear(");
obj.Accept(this, FuPriority.Argument);
if (args.Count == 1) {
// .NET Framework compatibility
Write(", 0, ");
WriteArrayStorageLength(obj, FuPriority.Argument);
}
}
else {
Write("Array.Fill(");
obj.Accept(this, FuPriority.Argument);
Write(", ");
WriteNotPromoted(obj.Type.AsClassType().GetElementType(), args[0]);
}
if (args.Count == 3) {
Write(", ");
args[1].Accept(this, FuPriority.Argument);
Write(", ");
args[2].Accept(this, FuPriority.Argument);
}
WriteChar(')');
break;
case FuId.ArraySortAll:
Include("System");
WriteCall("Array.Sort", obj);
break;
case FuId.ArraySortPart:
Include("System");
WriteCall("Array.Sort", obj, args[0], args[1]);
break;
case FuId.ListAdd:
case FuId.HashSetAdd:
case FuId.SortedSetAdd:
WriteListAdd(obj, "Add", args);
break;
case FuId.ListAll:
WriteMethodCall(obj, "TrueForAll", args[0]);
break;
case FuId.ListAny:
WriteMethodCall(obj, "Exists", args[0]);
break;
case FuId.ListInsert:
WriteListInsert(obj, "Insert", args);
break;
case FuId.ListLast:
WritePostfix(obj, "[^1]");
break;
case FuId.QueueEnqueue:
WriteListAdd(obj, "Enqueue", args);
break;
case FuId.StackPush:
WriteListAdd(obj, "Push", args);
break;
case FuId.ListSortPart:
WritePostfix(obj, ".Sort(");
WriteCoercedArgs(method, args);
Write(", null)");
break;
case FuId.PriorityQueueEnqueue:
WritePostfix(obj, ".Enqueue(");
WriteNotPromoted(obj.Type.AsClassType().GetElementType(), args[0]);
Write(", ");
WriteNotPromoted(obj.Type.AsClassType().GetValueType(), args[1]);
WriteChar(')');
break;
case FuId.DictionaryAdd:
WritePostfix(obj, ".Add(");
args[0].Accept(this, FuPriority.Argument);
Write(", ");
WriteNewStorage(obj.Type.AsClassType().GetValueType());
WriteChar(')');
break;
case FuId.TextWriterWrite:
case FuId.TextWriterWriteLine:
case FuId.ConsoleWrite:
case FuId.ConsoleWriteLine:
if (method.Id == FuId.ConsoleWrite || method.Id == FuId.ConsoleWriteLine)
Include("System");
obj.Accept(this, FuPriority.Primary);
WriteMemberOp(obj, null);
Write(method.Name);
WriteChar('(');
if (args.Count != 0) {
if (args[0] is FuLiteralChar) {
Write("(int) ");
args[0].Accept(this, FuPriority.Primary);
}
else
args[0].Accept(this, FuPriority.Argument);
}
WriteChar(')');
break;
case FuId.TextWriterWriteChar:
WriteCharMethodCall(obj, "Write", args[0]);
break;
case FuId.TextWriterWriteCodePoint:
WritePostfix(obj, ".Write(");
if (args[0] is FuLiteralChar literalChar && literalChar.Value < 0x10000)
args[0].Accept(this, FuPriority.Argument);
else {
Include("System.Text");
WriteCall("new Rune", args[0]);
}
WriteChar(')');
break;
case FuId.StringWriterClear:
WritePostfix(obj, ".GetStringBuilder().Clear()");
break;
case FuId.UTF8GetByteCount:
Include("System.Text");
WriteCall("Encoding.UTF8.GetByteCount", args[0]);
break;
case FuId.UTF8GetBytes:
Include("System.Text");
Write("Encoding.UTF8.GetBytes(");
args[0].Accept(this, FuPriority.Argument);
Write(", 0, ");
WritePostfix(args[0], ".Length, "); // FIXME: side effect
args[1].Accept(this, FuPriority.Argument);
Write(", ");
args[2].Accept(this, FuPriority.Argument);
WriteChar(')');
break;
case FuId.UTF8GetString:
Include("System.Text");
Write("Encoding.UTF8.GetString");
WriteInParentheses(args);
break;
case FuId.EnvironmentGetEnvironmentVariable:
Include("System");
Write("Environment.GetEnvironmentVariable");
WriteInParentheses(args);
break;
case FuId.DateTimeOffsetUtcNowToUnixTimeMilliseconds:
Include("System");
Write("DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()");
break;
case FuId.RegexCompile:
Include("System.Text.RegularExpressions");
Write("new Regex");
WriteInParentheses(args);
break;
case FuId.RegexEscape:
case FuId.RegexIsMatchStr:
case FuId.RegexIsMatchRegex:
Include("System.Text.RegularExpressions");
obj.Accept(this, FuPriority.Primary);
WriteChar('.');
Write(method.Name);
WriteInParentheses(args);
break;
case FuId.MatchFindStr:
Include("System.Text.RegularExpressions");
WriteChar('(');
obj.Accept(this, FuPriority.Assign);
Write(" = Regex.Match");
WriteInParentheses(args);
Write(").Success");
break;
case FuId.MatchFindRegex:
Include("System.Text.RegularExpressions");
WriteChar('(');
obj.Accept(this, FuPriority.Assign);
Write(" = ");
WriteMethodCall(args[1], "Match", args[0]);
Write(").Success");
break;
case FuId.MatchGetCapture:
WritePostfix(obj, ".Groups[");
args[0].Accept(this, FuPriority.Argument);
Write("].Value");
break;
case FuId.JsonElementParse:
Write("JsonDocument.Parse(");
args[0].Accept(this, FuPriority.Argument);
Write(").RootElement");
break;
case FuId.JsonElementIsObject:
WriteJsonElementIs(obj, "Object", parent);
break;
case FuId.JsonElementIsArray:
WriteJsonElementIs(obj, "Array", parent);
break;
case FuId.JsonElementIsString:
WriteJsonElementIs(obj, "String", parent);
break;
case FuId.JsonElementIsNumber:
WriteJsonElementIs(obj, "Number", parent);
break;
case FuId.JsonElementIsBoolean:
if (parent > FuPriority.CondOr)
WriteChar('(');
WritePostfix(obj, ".ValueKind == JsonValueKind.True || ");
WritePostfix(obj, ".ValueKind == JsonValueKind.False"); // FIXME: side effect
if (parent > FuPriority.CondOr)
WriteChar(')');
break;
case FuId.JsonElementIsNull:
WriteJsonElementIs(obj, "Null", parent);
break;
case FuId.JsonElementGetObject:
Include("System.Linq");
WritePostfix(obj, ".EnumerateObject().ToDictionary(p => p.Name, p => p.Value)");
break;
case FuId.JsonElementGetArray:
Include("System.Linq");
WritePostfix(obj, ".EnumerateArray().ToList()");
break;
case FuId.MathMethod:
case FuId.MathCeiling:
case FuId.MathFusedMultiplyAdd:
case FuId.MathLog2:
case FuId.MathRound:
case FuId.MathSqrt:
case FuId.MathTruncate:
Include("System");
Write("Math");
if (!args.Any(arg => arg.Type.Id == FuId.DoubleType))
WriteChar('F');
WriteChar('.');
Write(method.Name);
WriteInParentheses(args);
break;
case FuId.MathAbs:
case FuId.MathClamp:
case FuId.MathMax:
case FuId.MathMin:
Include("System");
Write("Math.");
Write(method.Name);
WriteInParentheses(args);
break;
case FuId.MathIsFinite:
case FuId.MathIsInfinity:
case FuId.MathIsNaN:
Write("double.");
WriteCall(method.Name, args[0]);
break;
default:
NotSupported(obj, method.Name);
break;
}
}
protected override void WriteOpAssignRight!(FuBinaryExpr expr)
{
WriteAssignRight(expr);
}
internal override void VisitBinaryExpr!(FuBinaryExpr expr, FuPriority parent)
{
switch (expr.Op) {
case FuToken.AndAssign:
case FuToken.OrAssign:
case FuToken.XorAssign:
if (parent > FuPriority.Assign)
WriteChar('(');
expr.Left.Accept(this, FuPriority.Assign);
WriteChar(' ');
Write(expr.GetOpString());
WriteChar(' ');
WriteAssignRight(expr);
if (parent > FuPriority.Assign)
WriteChar(')');
break;
default:
base.VisitBinaryExpr(expr, parent);
break;
}
}
internal override void VisitLambdaExpr!(FuLambdaExpr expr)
{
WriteName(expr.First);
Write(" => ");
expr.Body.Accept(this, FuPriority.Statement);
}
protected override void DefineObjectLiteralTemporary!(FuUnaryExpr expr)
{
}
protected override void DefineIsVar!(FuBinaryExpr binary)
{
}
protected override void WriteAssert!(FuAssert statement)
{
if (statement.CompletesNormally()) {
Include("System.Diagnostics");
Write("Debug.Assert(");
statement.Cond.Accept(this, FuPriority.Argument);
if (statement.Message != null) {
Write(", ");
statement.Message.Accept(this, FuPriority.Argument);
}
}
else {
// assert false;
Include("System");
Write("throw new NotImplementedException(");
if (statement.Message != null)
statement.Message.Accept(this, FuPriority.Argument);
}
WriteLine(");");
}
internal override void VisitForeach!(FuForeach statement)
{
Write("foreach (");
if (statement.Collection.Type is FuClassType dict && dict.Class.TypeParameterCount == 2) {
WriteChar('(');
WriteTypeAndName(statement.GetVar());
Write(", ");
WriteTypeAndName(statement.GetValueVar());
WriteChar(')');
}
else
WriteTypeAndName(statement.GetVar());
Write(" in ");
statement.Collection.Accept(this, FuPriority.Argument);
WriteChar(')');
WriteChild(statement.Body);
}
internal override void VisitLock!(FuLock statement)
{
WriteCall("lock ", statement.Lock);
WriteChild(statement.Body);
}
protected override void WriteException!()
{
Include("System");
Write("Exception");
}
protected override void WriteEnum!(FuEnum enu)
{
WriteNewLine();
WriteDoc(enu.Documentation);
if (enu is FuEnumFlags) {
Include("System");
WriteLine("[Flags]");
}
WritePublic(enu);
Write("enum ");
WriteLine(enu.Name);
OpenBlock();
enu.AcceptValues(this);
WriteNewLine();
CloseBlock();
}
protected override void WriteRegexOptionsEnum!(FuProgram program)
{
if (program.RegexOptionsEnum)
Include("System.Text.RegularExpressions");
}
protected override void WriteConst!(FuConst konst)
{
WriteNewLine();
WriteDoc(konst.Documentation);
WriteVisibility(konst.Visibility);
Write(konst.Type is FuArrayStorageType ? "static readonly " : "const ");
WriteTypeAndName(konst);
Write(" = ");
WriteCoercedExpr(konst.Type, konst.Value);
WriteCharLine(';');
}
protected override void WriteField!(FuField field)
{
WriteNewLine();
WriteDoc(field.Documentation);
WriteVisibility(field.Visibility);
if (field.Type.IsFinal() && !field.IsAssignableStorage())
Write("readonly ");
WriteVar(field);
if (!field.Type.Nullable && field.Type is FuClassType &&
(field.Type.IsFinal() ? field.Value is FuLiteralNull && !(field.Type is FuArrayStorageType): field.Value == null))
Write(" = null!");
WriteCharLine(';');
}
protected override void WriteParameterDoc!(FuVar param, bool first)
{
Write("/// <param name=\"");
WriteName(param);
Write("\">");
WriteDocPara(param.Documentation.Summary, false);
WriteLine("</param>");
}
protected override void WriteThrowsDoc!(FuThrowsDeclaration decl)
{
Write("/// <exception cref=\"");
WriteExceptionClass(decl.Symbol);
Write("\">");
WriteDocPara(decl.Documentation.Summary, false);
WriteLine("</exception>");
}
protected override bool IsShortMethod(FuMethod method) => method.Body is FuReturn;
protected override void WriteMethod!(FuMethod method)
{
if (method.Id == FuId.ClassToString && method.CallType == FuCallType.Abstract)
return;
WriteNewLine();
WriteDoc(method.Documentation);
WriteParametersAndThrowsDoc(method);
WriteVisibility(method.Visibility);
if (method.Id == FuId.ClassToString)
Write("override ");
else
WriteCallType(method.CallType, "sealed override ");
WriteTypeAndName(method);
WriteParameters(method, method.Id != FuId.Main);
WriteBody(method);
}
protected override void WriteClass!(FuClass klass, FuProgram program)
{
WriteNewLine();
WriteDoc(klass.Documentation);
WritePublic(klass);
WriteCallType(klass.CallType, "sealed ");
OpenClass(klass, "", " : ");
if (NeedsConstructor(klass)) {
if (klass.Constructor != null) {
WriteDoc(klass.Constructor.Documentation);
WriteVisibility(klass.Constructor.Visibility);
}
else
Write("internal ");
Write(klass.Name);
WriteLine("()");
OpenBlock();
WriteConstructorBody(klass);
CloseBlock();
}
else if (klass.Id == FuId.ExceptionClass) {
WriteExceptionConstructor(klass, "() { }");
WriteExceptionConstructor(klass, "(String message) : base(message) { }");
WriteExceptionConstructor(klass, "(String message, Exception innerException) : base(message, innerException) { }");
}
WriteMembers(klass, true);
CloseBlock();
}
void WriteLib!()
{
if (this.NewArray) {
Include("System");
WriteNewLine();
WriteLine("static class Fu");
OpenBlock();
WriteLine("public static T[] NewArray<T>(int size, Func<T> f)");
OpenBlock();
WriteLine("T[] a = new T[size];");
WriteLine("for (int i = 0; i < size; i++)");
WriteLine("\ta[i] = f();");
WriteLine("return a;");
CloseBlock();
CloseBlock();
}
}
void WriteResources!(SortedDictionary<string(), List<byte>()> resources)
{
WriteNewLine();
WriteLine("static class FuResource");
OpenBlock();
foreach ((string name, List<byte> content) in resources) {
Write("internal static readonly byte[] ");
WriteResourceName(name);
WriteLine(" = {");
WriteChar('\t');
WriteBytes(content);
WriteLine(" };");
}
CloseBlock();
}
public override void WriteProgram!(FuProgram program, string outputFile, string namespace)
{
this.NewArray = false;
OpenStringWriter();
if (namespace.Length != 0) {
Write("namespace ");
WriteLine(namespace);
OpenBlock();
}
WriteTopLevelNatives(program);
WriteTypes(program);
WriteLib();
if (program.Resources.Count > 0)
WriteResources(program.Resources);
if (namespace.Length != 0)
CloseBlock();
CreateFile(null, outputFile);
WriteLine("#nullable enable");
WriteIncludes("using ", ";");
CloseStringWriter();
CloseFile();
}
}