// GenD.fu - D code generator
//
// Copyright (C) 2011-2026  Piotr Fusik
// Copyright (C) 2023  Adrian Matoga
//
// 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 GenD : GenCCppD
{
	bool HasListInsert;
	bool HasListRemoveAt;
	bool HasQueueDequeue;
	bool HasStackPop;
	bool HasSortedDictionaryInsert;
	bool HasSortedDictionaryFind;
	bool BitConverter;

	protected override string GetTargetName() => "D";

	protected override void StartDocLine!()
	{
		Write("/// ");
	}

	protected override void WriteDocPara!(FuDocPara para, bool many)
	{
		if (many) {
			WriteNewLine();
			StartDocLine();
		}
		foreach (FuDocInline inline in para.Children) {
			switch (inline) {
			case FuDocText text:
				WriteXmlDoc(text.Text);
				break;
			case FuDocCode code:
				WriteChar('`');
				WriteXmlDoc(code.Text);
				WriteChar('`');
				break;
			case FuDocLine:
				WriteNewLine();
				StartDocLine();
				break;
			default:
				assert false;
			}
		}
		if (many)
			WriteNewLine();
	}

	protected override void WriteParameterDoc!(FuVar param, bool first)
	{
		if (first) {
			StartDocLine();
			WriteLine("Params:");
		}
		StartDocLine();
		WriteName(param);
		Write(" = ");
		WriteDocPara(param.Documentation.Summary, false);
		WriteNewLine();
	}

	protected override void WriteThrowsDoc!(FuThrowsDeclaration decl)
	{
		Write("/// Throws: ");
		WriteExceptionClass(decl.Symbol);
		WriteChar(' ');
		WriteDocPara(decl.Documentation.Summary, false);
		WriteNewLine();
	}

	protected override void WriteDocList!(FuDocList list)
	{
		WriteLine("///");
		WriteLine("/// <ul>");
		foreach (FuDocPara item in list.Items) {
			Write("/// <li>");
			WriteDocPara(item, false);
			WriteLine("</li>");
		}
		WriteLine("/// </ul>");
		Write("///");
	}

	protected override void WriteDoc!(FuCodeDoc? doc)
	{
		if (doc == null)
			return;
		StartDocLine();
		WriteDocPara(doc.Summary, false);
		WriteNewLine();
		if (doc.Details.Count > 0) {
			StartDocLine();
			if (doc.Details.Count == 1)
				WriteDocBlock(doc.Details[0], false);
			else {
				foreach (FuDocBlock block in doc.Details)
					WriteDocBlock(block, true);
			}
			WriteNewLine();
		}
	}

	protected override void WriteName!(FuSymbol symbol)
	{
		if (symbol is FuContainerType) {
			Write(symbol.Name);
			return;
		}
		WriteCamelCase(symbol.Name);
		switch (symbol.Name) {
		case "Abstract":
		case "Alias":
		case "Align":
		case "Asm":
		case "Assert":
		case "Auto":
		case "Body":
		case "Bool":
		case "Break":
		case "Byte":
		case "Case":
		case "Cast":
		case "Catch":
		case "Cdouble":
		case "Cent":
		case "Cfloat":
		case "Char":
		case "Class":
		case "Const":
		case "Continue":
		case "Creal":
		case "Dchar":
		case "Debug":
		case "Default":
		case "Delegate":
		case "Delete":
		case "Deprecated":
		case "Do":
		case "Double":
		case "Else":
		case "Enum":
		case "Export":
		case "Extern":
		case "False":
		case "Final":
		case "Finally":
		case "Float":
		case "For":
		case "Foreach":
		case "Foreach_reverse":
		case "Function":
		case "Goto":
		case "Idouble":
		case "If":
		case "IfLoat":
		case "Immutable":
		case "Import":
		case "In":
		case "Inout":
		case "Int":
		case "Interface":
		case "Invariant":
		case "Ireal":
		case "Is":
		case "Lazy":
		case "Long":
		case "Macro":
		case "Mixin":
		case "Module":
		case "New":
		case "Nothrow":
		case "Null":
		case "Out":
		case "Override":
		case "Package":
		case "Pragma":
		case "Private":
		case "Protected":
		case "Public":
		case "Pure":
		case "Real":
		case "Ref":
		case "Return":
		case "Scope":
		case "Shared":
		case "Short":
		case "Sizeof":
		case "Static":
		case "String":
		case "Struct":
		case "Super":
		case "Switch":
		case "Synchronized":
		case "Template":
		case "Throw":
		case "True":
		case "Try":
		case "Typeid":
		case "Typeof":
		case "Ubyte":
		case "Ucent":
		case "Uint":
		case "Ulong":
		case "Union":
		case "Unittest":
		case "Ushort":
		case "Version":
		case "Void":
		case "Wchar":
		case "While":
		case "With":
		case "alias":
		case "align":
		case "asm":
		case "auto":
		case "body":
		case "cast":
		case "catch":
		case "cdouble":
		case "cent":
		case "cfloat":
		case "char":
		case "creal":
		case "dchar":
		case "debug":
		case "delegate":
		case "delete":
		case "deprecated":
		case "export":
		case "extern":
		case "final":
		case "finally":
		case "foreach_reverse":
		case "function":
		case "goto":
		case "idouble":
		case "ifloat":
		case "immutable":
		case "import":
		case "in":
		case "inout":
		case "interface":
		case "invariant":
		case "ireal":
		case "lazy":
		case "macro":
		case "mixin":
		case "module":
		case "nothrow":
		case "out":
		case "package":
		case "pragma":
		case "private":
		case "pure":
		case "real":
		case "ref":
		case "scope":
		case "shared":
		case "sizeof":
		case "struct":
		case "super":
		case "synchronized":
		case "template":
		case "try":
		case "typeid":
		case "typeof":
		case "ubyte":
		case "ucent":
		case "uint":
		case "ulong":
		case "union":
		case "unittest":
		case "ushort":
		case "version":
		case "wchar":
		case "with":
		case "__FILE__":
		case "__FILE_FULL_PATH__":
		case "__MODULE__":
		case "__LINE__":
		case "__FUNCTION__":
		case "__PRETTY_FUNCTION__":
		case "__gshared":
		case "__traits":
		case "__vector":
		case "__parameters":
			WriteChar('_');
			break;
		default:
			break;
		}
	}

	protected override int GetLiteralChars() => 0x10000;

	void WriteVisibility!(FuVisibility visibility)
	{
		switch (visibility) {
		case FuVisibility.Private:
			Write("private ");
			break;
		case FuVisibility.Internal:
		case FuVisibility.Public:
			break;
		case FuVisibility.Protected:
			Write("protected ");
			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:
			break;
		case FuCallType.Override:
			Write("override ");
			break;
		case FuCallType.Sealed:
			Write(sealedString);
			break;
		}
	}

	static bool IsCreateWithNew(FuType type)
	{
		if (type is FuClassType klass) {
			if (klass is FuStorageType stg)
				return stg.Class.Id != FuId.ArrayStorageClass;
			return true;
		}
		return false;
	}

	static bool IsTransitiveConst(FuClassType array)
	{
		while (!(array is FuReadWriteClassType)) {
			if (!(array.GetElementType() is FuClassType element))
				return true;
			if (element.Class.Id != FuId.ArrayPtrClass)
				return false;
			array = element;
		}
		return false;
	}

	static bool IsJsonElementList(FuClassType list) => list.GetElementType() is FuClassType json && json.Class.Id == FuId.JsonElementClass;

	static bool IsStructPtr(FuType type) => type is FuClassType ptr
		&& (ptr.Class.Id == FuId.ListClass || ptr.Class.Id == FuId.StackClass || ptr.Class.Id == FuId.QueueClass)
		&& !IsJsonElementList(ptr);

	void WriteElementType!(FuType type)
	{
		WriteType(type, false);
		if (IsStructPtr(type))
			WriteChar('*');
	}

	protected override void WriteType!(FuType type, bool promote)
	{
		switch (type) {
		case FuIntegerType:
			switch (GetTypeId(type, promote)) {
			case FuId.SByteRange:
				Write("byte");
				break;
			case FuId.ByteRange:
				Write("ubyte");
				break;
			case FuId.ShortRange:
				Write("short");
				break;
			case FuId.UShortRange:
				Write("ushort");
				break;
			case FuId.IntType:
				Write("int");
				break;
			case FuId.NIntType:
				Write("ptrdiff_t");
				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:
				if (promote && IsTransitiveConst(klass)) {
					Write("const(");
					WriteElementType(klass.GetElementType());
					WriteChar(')');
				}
				else
					WriteElementType(klass.GetElementType());
				WriteChar('[');
				if (klass.Id != FuId.MainArgsType && klass is FuArrayStorageType arrayStorage)
					VisitLiteralLong(arrayStorage.Length, FuPriority.Argument);
				WriteChar(']');
				break;
			case FuId.ListClass:
			case FuId.StackClass:
				if (IsJsonElementList(klass)) {
					Include("std.json");
					Write("JSONValue[]");
				}
				else {
					Include("std.container.array");
					Write("Array!(");
					WriteElementType(klass.GetElementType());
					WriteChar(')');
				}
				break;
			case FuId.QueueClass:
				Include("std.container.dlist");
				Write("DList!(");
				WriteElementType(klass.GetElementType());
				WriteChar(')');
				break;
			case FuId.HashSetClass:
				Write("bool[");
				WriteElementType(klass.GetElementType());
				WriteChar(']');
				break;
			case FuId.DictionaryClass:
				WriteElementType(klass.GetValueType());
				WriteChar('[');
				WriteType(klass.GetKeyType(), false);
				WriteChar(']');
				break;
			case FuId.SortedSetClass:
				Include("std.container.rbtree");
				Write("RedBlackTree!(");
				WriteElementType(klass.GetElementType());
				WriteChar(')');
				break;
			case FuId.SortedDictionaryClass:
				Include("std.container.rbtree");
				Include("std.typecons");
				Write("RedBlackTree!(Tuple!(");
				WriteElementType(klass.GetKeyType());
				Write(", ");
				WriteElementType(klass.GetValueType());
				Write("), \"a[0] < b[0]\")");
				break;
			case FuId.OrderedDictionaryClass:
				Include("std.typecons");
				Write("Tuple!(Array!(");
				WriteElementType(klass.GetValueType());
				Write("), \"data\", size_t[");
				WriteType(klass.GetKeyType(), false);
				Write("], \"dict\")");
				break;
			case FuId.TextWriterClass:
				Include("std.stdio");
				Write("File");
				break;
			case FuId.RegexClass:
				Include("std.regex");
				Write("Regex!char");
				break;
			case FuId.MatchClass:
				Include("std.regex");
				Write("Captures!string");
				break;
			case FuId.JsonElementClass:
				Include("std.json");
				Write("JSONValue");
				break;
			case FuId.LockClass:
				Write("Object");
				break;
			default:
				Write(klass.Class.Name);
				break;
			}
			break;
		default:
			Write(type.Name);
			break;
		}
	}

	protected override void WriteTypeAndName!(FuNamedValue value)
	{
		WriteType(value.Type, true);
		if (IsStructPtr(value.Type))
			WriteChar('*');
		WriteChar(' ');
		WriteName(value);
	}

	internal override void VisitAggregateInitializer!(FuAggregateInitializer expr)
	{
		Write("[ ");
		WriteCoercedLiterals(expr.Type.AsClassType().GetElementType(), expr.Items);
		Write(" ]");
	}

	protected override void WriteStaticCast!(FuType type, FuExpr expr)
	{
		Write("cast(");
		WriteType(type, false);
		Write(") ");
		GetStaticCastInner(type, expr).Accept(this, FuPriority.Primary);
	}

	protected override void WriteInterpolatedStringArg!(FuInterpolatedPart part)
	{
		if (part.Format == 'U' || part.Format == 'u') {
			Write("cast(dchar) ");
			part.Argument.Accept(this, FuPriority.Primary);
		}
		else
			base.WriteInterpolatedStringArg(part);
	}

	internal override void VisitInterpolatedString!(FuInterpolatedString expr, FuPriority parent)
	{
		Include("std.format");
		Write("format(");
		WritePrintf(expr, false);
	}

	protected override void WriteStorageInit!(FuNamedValue def)
	{
		Write(" = ");
		WriteNewStorage(def.Type);
	}

	protected override void WriteVarInit!(FuNamedValue def)
	{
		if (def.Type is FuArrayStorageType)
			return;
		base.WriteVarInit(def);
	}

	protected override bool HasInitCode(FuNamedValue def)
	{
		if (def.Value != null && !(def.Value is FuLiteral))
			return true;
		FuType type = def.Type;
		if (type is FuArrayStorageType array) {
			while (array.GetElementType() is FuArrayStorageType innerArray)
				array = innerArray;
			type = array.GetElementType();
		}
		return type is FuStorageType;
	}

	protected override void WriteInitField!(FuField field)
	{
		WriteInitCode(field);
	}

	protected override void WriteInitCode!(FuNamedValue def)
	{
		if (!HasInitCode(def))
			return;
		if (def.Type is FuArrayStorageType array) {
			int nesting = 0;
			while (array.GetElementType() is FuArrayStorageType innerArray) {
				OpenLoop("size_t", nesting++, array.Length);
				array = innerArray;
			}
			if (array.GetElementType() is FuStorageType klass) {
				OpenLoop("size_t", nesting++, array.Length);
				WriteArrayElement(def, nesting);
				Write(" = ");
				WriteNew(klass, FuPriority.Argument);
				WriteCharLine(';');
			}
			while (--nesting >= 0)
				CloseBlock();
		}
		else {
			if (def.Type is FuReadWriteClassType klass) {
				switch (klass.Class.Id) {
				case FuId.StringClass:
				case FuId.ArrayPtrClass:
				case FuId.ArrayStorageClass:
				case FuId.HashSetClass:
				case FuId.DictionaryClass:
				case FuId.SortedDictionaryClass:
				case FuId.OrderedDictionaryClass:
				case FuId.RegexClass:
				case FuId.MatchClass:
				case FuId.LockClass:
					break;
				default:
					if (def.Parent is FuClass) {
						WriteName(def);
						Write(" = ");
						if (def.Value == null)
							WriteNew(klass, FuPriority.Argument);
						else
							WriteCoercedExpr(def.Type, def.Value);
						WriteCharLine(';');
					}
					base.WriteInitCode(def);
					break;
				}
			}
		}
	}

	protected override void WriteNewArray!(FuType elementType, FuExpr lengthExpr, FuPriority parent)
	{
		Write("new ");
		WriteType(elementType, false);
		WriteChar('[');
		lengthExpr.Accept(this, FuPriority.Argument);
		WriteChar(']');
	}

	void WriteStaticInitializer!(FuType type)
	{
		WriteChar('(');
		WriteType(type, false);
		Write(").init");
	}

	protected override void WriteNew!(FuReadWriteClassType klass, FuPriority parent)
	{
		if (IsCreateWithNew(klass)) {
			Write("new ");
			WriteType(klass, false);
			if (parent == FuPriority.Primary)
				Write("()");
		}
		else
			WriteStaticInitializer(klass);
	}

	protected override void WriteResource!(string name, int length)
	{
		Write("FuResource.");
		WriteResourceName(name);
	}

	protected override void WriteStringLength!(FuExpr expr)
	{
		WritePostfix(expr, ".length");
	}

	void WriteClassReference!(FuExpr expr, FuPriority priority = FuPriority.Primary)
	{
		if (IsStructPtr(expr.Type)) {
			Write("(*");
			expr.Accept(this, priority);
			WriteChar(')');
		}
		else
			expr.Accept(this, priority);
	}

	internal override void VisitSymbolReference!(FuSymbolReference expr, FuPriority parent)
	{
		switch (expr.Symbol.Id) {
		case FuId.ConsoleError:
			Write("stderr");
			break;
		case FuId.ConsoleOut:
			Write("stdout");
			break;
		case FuId.ListCount:
		case FuId.StackCount:
		case FuId.HashSetCount:
		case FuId.SortedSetCount:
		case FuId.DictionaryCount:
		case FuId.SortedDictionaryCount:
			WriteStringLength(expr.Left);
			break;
		case FuId.QueueCount:
			Include("std.range");
			WriteClassReference(expr.Left);
			Write("[].walkLength");
			break;
		case FuId.MatchStart:
			WritePostfix(expr.Left, ".pre.length");
			break;
		case FuId.MatchEnd:
			if (parent > FuPriority.Add)
				WriteChar('(');
			WritePostfix(expr.Left, ".pre.length + ");
			WritePostfix(expr.Left, ".hit.length"); // FIXME: side effect
			if (parent > FuPriority.Add)
				WriteChar(')');
			break;
		case FuId.MatchLength:
			WritePostfix(expr.Left, ".hit.length");
			break;
		case FuId.MatchValue:
			WritePostfix(expr.Left, ".hit");
			break;
		case FuId.MathNaN:
			Write("double.nan");
			break;
		case FuId.MathNegativeInfinity:
			Write("-double.infinity");
			break;
		case FuId.MathPositiveInfinity:
			Write("double.infinity");
			break;
		default:
			// assert !(expr.Symbol.Parent is FuForeach forEach
			// && forEach.Collection.Type is FuClassType dict
			// && dict.Class.Id == FuId.OrderedDictionaryClass);
			base.VisitSymbolReference(expr, parent);
			break;
		}
	}

	void WriteTryParseAssign!(FuExpr obj, List<FuExpr#> args)
	{
		obj.Accept(this, FuPriority.Assign);
		Write(" = ");
		Include("std.conv");
		WritePostfix(args[0], ".to!");
		WriteType(obj.Type, false);
		if (args.Count == 2) {
			WriteChar('(');
			args[1].Accept(this, FuPriority.Argument);
			WriteChar(')');
		}
	}

	protected override void WriteTryParseStatement!(FuCallExpr call, FuStatement!? onParsed, FuStatement!? onFailure)
	{
		Write("try ");
		OpenBlock();
		WriteTryParseAssign(call.Method.Left, call.Arguments);
		EndStatement();
		EndTryParse(call, onParsed, "catch (ConvException e_) ", onFailure);
	}

	void WriteWrite!(List<FuExpr#> args, bool newLine)
	{
		Include("std.stdio");
		if (args.Count == 0)
			Write("writeln()");
		else if (args[0] is FuInterpolatedString interpolated) {
			Write(newLine ? "writefln(" : "writef(");
			WritePrintf(interpolated, false);
		}
		else
			WriteCall(newLine ? "writeln" : "write", args[0]);
	}

	void WriteSlice!(FuExpr obj, FuExpr offset, FuExpr? length)
	{
		WriteClassReference(obj, FuPriority.Primary);
		WriteChar('[');
		if (!offset.IsLiteralZero() || length != null) {
			offset.Accept(this, FuPriority.Argument);
			Write(" .. ");
			if (length == null)
				WriteChar('$');
			else if (offset is FuLiteralLong)
				WriteAdd(offset, length);
			else {
				Write("$][0 .. ");
				length.Accept(this, FuPriority.Argument);
			}
		}
		WriteChar(']');
	}

	void WriteInsertedArg!(FuType type, List<FuExpr#> args, int index = 0)
	{
		if (args.Count <= index) {
			assert type is FuReadWriteClassType klass;
			WriteNew(klass, FuPriority.Argument);
		}
		else
			WriteCoercedExpr(type, args[index]);
		WriteChar(')');
	}

	void WriteJsonElementIs!(FuExpr obj, string name, FuPriority parent)
	{
		if (parent > FuPriority.Equality)
			WriteChar('(');
		WritePostfix(obj, ".type == JSONType.");
		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.ListClear:
		case FuId.QueueClear:
		case FuId.StackClear:
		case FuId.HashSetRemove:
		case FuId.DictionaryRemove:
		case FuId.TextWriterFlush:
			if (obj != null) {
				if (IsReferenceTo(obj, FuId.BasePtr))
					Write("super.");
				else {
					WriteClassReference(obj);
					WriteChar('.');
				}
			}
			WriteName(method);
			WriteCoercedArgsInParentheses(method, args);
			break;
		case FuId.EnumFromInt:
			WriteStaticCast(type, args[0]);
			break;
		case FuId.EnumHasFlag:
			WriteEnumHasFlag(obj, args, parent);
			break;
		case FuId.EnumToInt:
			obj.Accept(this, parent);
			break;
		case FuId.IntTryParse:
		case FuId.NIntTryParse:
		case FuId.LongTryParse:
		case FuId.FloatTryParse:
		case FuId.DoubleTryParse:
			Include("std.conv");
			Write("() { try { ");
			WriteTryParseAssign(obj, args);
			Write("; return true; } catch (ConvException e_) { ");
			obj.Accept(this, FuPriority.Assign);
			Write(" = 0; return false; } }()");
			break;
		case FuId.StringContains:
			Include("std.algorithm");
			WriteMethodCall(obj, "canFind", args[0]);
			break;
		case FuId.StringEndsWith:
			Include("std.string");
			WriteMethodCall(obj, "endsWith", args[0]);
			break;
		case FuId.StringIndexOf:
			Include("std.string");
			WriteMethodCall(obj, "indexOf", args[0]);
			break;
		case FuId.StringLastIndexOf:
			Include("std.string");
			WriteMethodCall(obj, "lastIndexOf", args[0]);
			break;
		case FuId.StringReplace:
			Include("std.string");
			WriteMethodCall(obj, "replace", args[0], args[1]);
			break;
		case FuId.StringStartsWith:
			Include("std.string");
			WriteMethodCall(obj, "startsWith", args[0]);
			break;
		case FuId.StringSubstring:
			WriteSlice(obj, args[0], args.Count == 2 ? args[1] : null);
			break;
		case FuId.StringToLower:
			Include("std.uni");
			WritePostfix(obj, ".toLower()");
			break;
		case FuId.StringToUpper:
			Include("std.uni");
			WritePostfix(obj, ".toUpper()");
			break;
		case FuId.ArrayBinarySearchAll:
		case FuId.ArrayBinarySearchPart:
			Include("std.range");
			Write("() { size_t fubegin = ");
			if (args.Count == 3)
				args[1].Accept(this, FuPriority.Argument);
			else
				WriteChar('0');
			Write("; auto fusearch = ");
			WriteClassReference(obj);
			WriteChar('[');
			if (args.Count == 3) {
				Write("fubegin .. fubegin + ");
				args[2].Accept(this, FuPriority.Add);
			}
			Write("].assumeSorted.trisect(");
			WriteNotPromoted(obj.Type.AsClassType().GetElementType(), args[0]);
			Write("); return fusearch[1].length ? fubegin + fusearch[0].length : -1; }()");
			break;
		case FuId.ArrayContains:
		case FuId.ListContains:
			Include("std.algorithm");
			WriteClassReference(obj);
			WriteCall("[].canFind", args[0]);
			break;
		case FuId.ArrayCopyTo:
		case FuId.ListCopyTo:
			Include("std.algorithm");
			WriteSlice(obj, args[0], args[3]);
			Write(".copy(");
			WriteSlice(args[1], args[2], null);
			WriteChar(')');
			break;
		case FuId.ArrayFillAll:
		case FuId.ArrayFillPart:
			Include("std.algorithm");
			if (args.Count == 3)
				WriteSlice(obj, args[1], args[2]);
			else {
				WriteClassReference(obj);
				Write("[]");
			}
			Write(".fill(");
			WriteNotPromoted(obj.Type.AsClassType().GetElementType(), args[0]);
			WriteChar(')');
			break;
		case FuId.ArraySortAll:
		case FuId.ArraySortPart:
		case FuId.ListSortAll:
		case FuId.ListSortPart:
			Include("std.algorithm");
			if (args.Count == 2)
				WriteSlice(obj, args[0], args[1]);
			else {
				WriteClassReference(obj);
				Write("[]");
			}
			Write(".sort");
			break;
		case FuId.ListAdd:
		case FuId.QueueEnqueue:
			WritePostfix(obj, ".insertBack(");
			WriteInsertedArg(obj.Type.AsClassType().GetElementType(), args);
			break;
		case FuId.ListAddRange:
			WriteClassReference(obj);
			Write(" ~= ");
			WriteClassReference(args[0]);
			Write("[]");
			break;
		case FuId.ListAll:
			Include("std.algorithm");
			WriteClassReference(obj);
			WriteCall("[].all!", args[0]);
			break;
		case FuId.ListAny:
			Include("std.algorithm");
			WriteClassReference(obj);
			Write("[].any!(");
			args[0].Accept(this, FuPriority.Argument);
			WriteChar(')');
			break;
		case FuId.ListInsert:
			this.HasListInsert = true;
			WritePostfix(obj, ".insertInPlace(");
			args[0].Accept(this, FuPriority.Argument);
			Write(", ");
			WriteInsertedArg(obj.Type.AsClassType().GetElementType(), args, 1);
			break;
		case FuId.ListLast:
			WritePostfix(obj, ".back");
			break;
		case FuId.ListRemoveAt:
		case FuId.ListRemoveRange:
			this.HasListRemoveAt = true;
			WritePostfix(obj, ".removeAt");
			WriteInParentheses(args);
			break;
		case FuId.ListIndexOf:
			Include("std.algorithm");
			WriteClassReference(obj);
			WriteCall("[].countUntil", args[0]);
			break;
		case FuId.QueueDequeue:
			this.HasQueueDequeue = true;
			Include("std.container.dlist");
			WriteClassReference(obj);
			Write(".dequeue()");
			break;
		case FuId.QueuePeek:
			WritePostfix(obj, ".front");
			break;
		case FuId.StackPeek:
			WritePostfix(obj, ".back");
			break;
		case FuId.StackPush:
			WriteClassReference(obj);
			Write(" ~= ");
			WriteCoercedExpr(obj.Type.AsClassType().GetElementType(), args[0]);
			break;
		case FuId.StackPop:
			this.HasStackPop = true;
			WriteClassReference(obj);
			Write(".pop()");
			break;
		case FuId.HashSetAdd:
			WritePostfix(obj, ".require(");
			WriteCoercedExpr(obj.Type.AsClassType().GetElementType(), args[0]);
			Write(", true)");
			break;
		case FuId.HashSetClear:
		case FuId.SortedSetClear:
		case FuId.DictionaryClear:
		case FuId.SortedDictionaryClear:
			WritePostfix(obj, ".clear()");
			break;
		case FuId.HashSetContains:
		case FuId.SortedSetContains:
		case FuId.DictionaryContainsKey:
			WriteContains(obj, args[0], parent);
			break;
		case FuId.SortedSetAdd:
			WritePostfix(obj, ".insert(");
			WriteInsertedArg(obj.Type.AsClassType().GetElementType(), args, 0);
			break;
		case FuId.SortedSetRemove:
			WriteMethodCall(obj, "removeKey", args[0]);
			break;
		case FuId.DictionaryAdd:
			if (obj.Type.AsClassType().Class.Id == FuId.SortedDictionaryClass) {
				this.HasSortedDictionaryInsert = true;
				WritePostfix(obj, ".replace(");
			}
			else
				WritePostfix(obj, ".require(");
			args[0].Accept(this, FuPriority.Argument);
			Write(", ");
			WriteInsertedArg(obj.Type.AsClassType().GetValueType(), args, 1);
			break;
		case FuId.SortedDictionaryContainsKey:
			if (parent > FuPriority.And)
				WriteChar('(');
			Write("tuple(");
			args[0].Accept(this, FuPriority.Argument);
			Write(", ");
			WriteStaticInitializer(obj.Type.AsClassType().GetValueType());
			Write(") in ");
			WriteClassReference(obj);
			if (parent > FuPriority.And)
				WriteChar(')');
			break;
		case FuId.SortedDictionaryRemove:
			WriteClassReference(obj);
			Write(".removeKey(tuple(");
			args[0].Accept(this, FuPriority.Argument);
			Write(", ");
			WriteStaticInitializer(obj.Type.AsClassType().GetValueType());
			Write("))");
			break;
		case FuId.TextWriterWrite:
		case FuId.TextWriterWriteLine:
			WritePostfix(obj, ".");
			WriteWrite(args, method.Id == FuId.TextWriterWriteLine);
			break;
		case FuId.TextWriterWriteChar:
			WritePostfix(obj, ".write(");
			if (!(args[0] is FuLiteralChar))
				Write("cast(char) ");
			args[0].Accept(this, FuPriority.Primary);
			WriteChar(')');
			break;
		case FuId.TextWriterWriteCodePoint:
			WritePostfix(obj, ".write(cast(dchar) ");
			args[0].Accept(this, FuPriority.Primary);
			WriteChar(')');
			break;
		case FuId.ConsoleReadLine:
			Include("std.stdio");
			Include("std.string");
			Write("readln.chomp");
			break;
		case FuId.ConsoleWrite:
		case FuId.ConsoleWriteLine:
			Write("stdout.");
			WriteWrite(args, method.Id == FuId.ConsoleWriteLine);
			break;
		case FuId.EnvironmentGetEnvironmentVariable:
			Include("std.process");
			WriteCall("environment.get", args[0]);
			break;
		case FuId.DateTimeOffsetUtcNowToUnixTimeMilliseconds:
			Include("std.datetime");
			Write("(Clock.currTime() - SysTime(unixTimeToStdTime(0))).total!\"msecs\"");
			break;
		case FuId.BitConverterInt32BitsToSingle:
		case FuId.BitConverterInt64BitsToDouble:
		case FuId.BitConverterSingleToInt32Bits:
		case FuId.BitConverterDoubleToInt64Bits:
			this.BitConverter = true;
			Write("fuBitCast!(");
			WriteType(type, false);
			Write(", ");
			WriteType(method.FirstParameter().Type, false);
			Write(")(");
			args[0].Accept(this, FuPriority.Argument);
			WriteChar(')');
			break;
		case FuId.ConvertToBase64String:
			Include("std.base64");
			Write("Base64.encode(");
			if (IsWholeArray(args[0], args[1], args[2]))
				args[0].Accept(this, FuPriority.Argument);
			else
				WriteSlice(args[0], args[1], args[2]);
			WriteChar(')');
			break;
		case FuId.UTF8GetByteCount:
			WritePostfix(args[0], ".length");
			break;
		case FuId.UTF8GetBytes:
			Include("std.string");
			Include("std.algorithm");
			WritePostfix(args[0], ".representation.copy(");
			WriteSlice(args[1], args[2], null);
			WriteChar(')');
			break;
		case FuId.UTF8GetString:
			Write("cast(string) ");
			WriteSlice(args[0], args[1], args[2]);
			Write(".idup");
			break;
		case FuId.RegexCompile:
			Include("std.regex");
			Write("regex(");
			args[0].Accept(this, FuPriority.Argument);
			WriteRegexOptions(args, ", \"", "", "\"", "i", "m", "s");
			WriteChar(')');
			break;
		case FuId.RegexEscape:
			Include("std.regex");
			Include("std.conv");
			WritePostfix(args[0], ".escaper.to!string");
			break;
		case FuId.RegexIsMatchRegex:
			Include("std.regex");
			WritePostfix(args[0], ".matchFirst(");
			(args.Count > 1 ? args[1] : obj).Accept(this, FuPriority.Argument);
			WriteChar(')');
			break;
		case FuId.RegexIsMatchStr:
			Include("std.regex");
			WritePostfix(args[0], ".matchFirst(");
			if (GetRegexOptions(args) != RegexOptions.None)
				Write("regex(");
			(args.Count > 1 ? args[1] : obj).Accept(this, FuPriority.Argument);
			WriteRegexOptions(args, ", \"", "", "\")", "i", "m", "s");
			WriteChar(')');
			break;
		case FuId.MatchFindStr:
			Include("std.regex");
			WriteChar('(');
			obj.Accept(this, FuPriority.Assign);
			Write(" = ");
			args[0].Accept(this, FuPriority.Primary);
			Write(".matchFirst(");
			if (GetRegexOptions(args) != RegexOptions.None)
				Write("regex(");
			args[1].Accept(this, FuPriority.Argument);
			WriteRegexOptions(args, ", \"", "", "\")", "i", "m", "s");
			Write("))");
			break;
		case FuId.MatchFindRegex:
			Include("std.regex");
			WriteChar('(');
			obj.Accept(this, FuPriority.Assign);
			Write(" = ");
			WriteMethodCall(args[0], "matchFirst", args[1]);
			WriteChar(')');
			break;
		case FuId.MatchGetCapture:
			WriteIndexing(obj, args[0]);
			break;
		case FuId.JsonElementParse:
			WriteCall("parseJSON", args[0]);
			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, "float_", parent);
			break;
		case FuId.JsonElementIsBoolean:
			if (parent > FuPriority.CondOr)
				WriteChar('(');
			WritePostfix(obj, ".type == JSONType.true_ || ");
			WritePostfix(obj, ".type == JSONType.false_"); // FIXME: side effect
			if (parent > FuPriority.CondOr)
				WriteChar(')');
			break;
		case FuId.JsonElementIsNull:
			WriteJsonElementIs(obj, "null_", parent);
			break;
		case FuId.JsonElementGetObject:
			WritePostfix(obj, ".object");
			break;
		case FuId.JsonElementGetArray:
			WritePostfix(obj, ".array");
			break;
		case FuId.JsonElementGetString:
			WritePostfix(obj, ".str");
			break;
		case FuId.JsonElementGetDouble:
			WritePostfix(obj, ".get!double");
			break;
		case FuId.JsonElementGetBoolean:
			WritePostfix(obj, ".boolean");
			break;
		case FuId.MathMethod:
		case FuId.MathLog2:
		case FuId.MathSqrt:
			Include("std.math");
			WriteMathFloating(type, method, args);
			break;
		case FuId.MathAbs:
		case FuId.MathIsFinite:
		case FuId.MathIsInfinity:
		case FuId.MathIsNaN:
		case FuId.MathRound:
			Include("std.math");
			WriteCamelCase(method.Name);
			WriteInParentheses(args);
			break;
		case FuId.MathCeiling:
			Include("std.math");
			WriteCall("ceil", args[0]);
			break;
		case FuId.MathClamp:
		case FuId.MathMax:
		case FuId.MathMin:
			Include("std.algorithm");
			WriteLowercase(method.Name);
			WriteInParentheses(args);
			break;
		case FuId.MathFusedMultiplyAdd:
			Include("std.math");
			WriteCall("fma", args[0], args[1], args[2]);
			break;
		case FuId.MathTruncate:
			Include("std.math");
			WriteCall("trunc", args[0]);
			break;
		default:
			NotSupported(obj, method.Name);
			break;
		}
	}

	protected override void WriteIndexingExpr!(FuBinaryExpr expr, FuPriority parent)
	{
		WriteClassReference(expr.Left);
		assert expr.Left.Type is FuClassType klass;
		switch (klass.Class.Id) {
		case FuId.ArrayPtrClass:
		case FuId.ArrayStorageClass:
		case FuId.ListClass:
		case FuId.DictionaryClass:
			WriteChar('[');
			expr.Right.Accept(this, FuPriority.Argument);
			WriteChar(']');
			break;
		case FuId.SortedDictionaryClass:
			assert parent != FuPriority.Assign;
			this.HasSortedDictionaryFind = true;
			Include("std.container.rbtree");
			Include("std.typecons");
			Write(".find(");
			WriteStronglyCoerced(klass.GetKeyType(), expr.Right);
			WriteChar(')');
			break;
		case FuId.OrderedDictionaryClass:
			NotSupported(expr, "OrderedDictionary");
			break;
		default:
			assert false;
		}
	}

	static bool IsIsComparable(FuExpr expr) => expr is FuLiteralNull || (expr.Type is FuClassType klass && klass.Class.Id == FuId.ArrayPtrClass);

	protected override void WriteEqual!(FuExpr left, FuExpr right, FuPriority parent, bool not)
	{
		if (IsIsComparable(left) || IsIsComparable(right))
			WriteEqualExpr(left, right, parent, not ? " !is " : " is ");
		else
			base.WriteEqual(left, right, parent, not);
	}

	protected override void WriteAssign!(FuBinaryExpr expr, FuPriority parent)
	{
		if (expr.Left is FuBinaryExpr indexing
		 && indexing.Op == FuToken.LeftBracket
		 && indexing.Left.Type is FuClassType dict) {
			switch (dict.Class.Id) {
			case FuId.SortedDictionaryClass:
				this.HasSortedDictionaryInsert = true;
				WritePostfix(indexing.Left, ".replace(");
				indexing.Right.Accept(this, FuPriority.Argument);
				Write(", ");
				WriteNotPromoted(expr.Type, expr.Right);
				WriteChar(')');
				return;
			default:
				break;
			}
		}
		base.WriteAssign(expr, parent);
	}

	void WriteIsVar!(FuExpr left, FuExpr right, FuPriority parent)
	{
		if (parent > FuPriority.Equality)
			WriteChar('(');
		switch (right) {
		case FuSymbolReference symbol when symbol.Symbol is FuClass klass:
			Write("cast(");
			Write(klass.Name);
			Write(") ");
			left.Accept(this, FuPriority.Primary);
			break;
		case FuVar def:
			WriteChar('(');
			WriteName(def);
			Write(" = cast(");
			Write(def.Type.Name);
			Write(") ");
			left.Accept(this, FuPriority.Primary);
			WriteChar(')');
			break;
		default:
			assert false;
		}
		Write(" !is null");
		if (parent > FuPriority.Equality)
			WriteChar(')');
	}

	internal override void VisitBinaryExpr!(FuBinaryExpr expr, FuPriority parent)
	{
		switch (expr.Op) {
		case FuToken.Is:
			WriteIsVar(expr.Left, expr.Right, parent >= FuPriority.Or && parent <= FuPriority.Mul ? FuPriority.Primary : parent);
			return;
		case FuToken.Plus:
			if (expr.Type.Id == FuId.StringStorageType) {
				expr.Left.Accept(this, FuPriority.Assign);
				Write(" ~ ");
				expr.Right.Accept(this, FuPriority.Assign);
				return;
			}
			break;
		case FuToken.AddAssign:
			if (expr.Left.Type.Id == FuId.StringStorageType) {
				expr.Left.Accept(this, FuPriority.Assign);
				Write(" ~= ");
				WriteAssignRight(expr);
				return;
			}
			break;
		default:
			break;
		}
		base.VisitBinaryExpr(expr, parent);
	}

	internal override void VisitLambdaExpr!(FuLambdaExpr expr)
	{
		WriteName(expr.First);
		Write(" => ");
		expr.Body.Accept(this, FuPriority.Statement);
	}

	internal override void VisitExpr!(FuExpr statement)
	{
		if (!TryWriteTryParse(statement, null, null))
			base.VisitExpr(statement);
	}

	protected override void WriteAssert!(FuAssert statement)
	{
		Write("assert(");
		statement.Cond.Accept(this, FuPriority.Argument);
		if (statement.Message != null) {
			Write(", ");
			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) {
			WriteTypeAndName(statement.GetVar());
			Write(", ");
			WriteTypeAndName(statement.GetValueVar());
		}
		else
			WriteTypeAndName(statement.GetVar());
		Write("; ");
		WriteClassReference(statement.Collection);
		if (statement.Collection.Type is FuClassType set && set.Class.Id == FuId.HashSetClass)
			Write(".byKey");
		WriteChar(')');
		WriteChild(statement.Body);
	}

	internal override void VisitIf!(FuIf statement)
	{
		if (!TryWriteIfTryParse(statement))
			base.VisitIf(statement);
	}

	internal override void VisitLock!(FuLock statement)
	{
		WriteCall("synchronized ", statement.Lock);
		WriteChild(statement.Body);
	}

	protected override void WriteSwitchCaseTypeVar!(FuExpr value)
	{
		DefineVar(value);
	}

	protected override void WriteSwitchCaseCond!(FuSwitch statement, FuExpr value, FuPriority parent)
	{
		switch (value) {
		case FuSymbolReference symbol when symbol.Symbol is FuClass:
			WriteIsVar(statement.Value, value, parent); // FIXME: side effect in every if
			break;
		case FuVar:
			WriteIsVar(statement.Value, value, parent); // FIXME: side effect in every if
			break;
		default:
			base.WriteSwitchCaseCond(statement, value, parent);
			break;
		}
	}

	internal override void VisitSwitch!(FuSwitch statement)
	{
		WriteTemporaries(statement.Value);
		if (statement.IsTypeMatching() || statement.HasWhen())
			WriteSwitchAsIfsWithGoto(statement);
		else {
			StartSwitch(statement);
			WriteLine("default:");
			this.Indent++;
			if (statement.DefaultBody.Count > 0)
				WriteSwitchCaseBody(statement.DefaultBody);
			else
				WriteLine("assert(false);");
			this.Indent--;
			WriteCharLine('}');
		}
	}

	protected override void WriteThrowMessage!(FuExpr? expr)
	{
		if (expr == null)
			Write("\"\"");
		else
			expr.Accept(this, FuPriority.Argument);
	}

	protected override void WriteEnum!(FuEnum enu)
	{
		WriteNewLine();
		WriteDoc(enu.Documentation);
		WritePublic(enu);
		Write("enum ");
		Write(enu.Name);
		OpenBlock();
		enu.AcceptValues(this);
		WriteNewLine();
		CloseBlock();
	}

	protected override void WriteConst!(FuConst konst)
	{
		WriteDoc(konst.Documentation);
		Write("static immutable ");
		WriteTypeAndName(konst);
		Write(" = ");
		WriteCoercedExpr(konst.Type, konst.Value);
		WriteCharLine(';');
	}

	protected override void WriteField!(FuField field)
	{
		WriteNewLine();
		WriteDoc(field.Documentation);
		WriteVisibility(field.Visibility);
		WriteTypeAndName(field);
		if (field.Value is FuLiteral) {
			Write(" = ");
			WriteCoercedExpr(field.Type, field.Value);
		}
		WriteCharLine(';');
	}

	protected override bool IsShortMethod(FuMethod method) => method.Body is FuReturn ret && !HasTemporaries(ret.Value);

	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 if (method.IsPure())
			Write("static pure ");
		else
			WriteCallType(method.CallType, "final override ");
		WriteTypeAndName(method);
		WriteParameters(method, true);
		WriteBody(method);
	}

	protected override void WriteClass!(FuClass klass, FuProgram program)
	{
		WriteNewLine();
		WriteDoc(klass.Documentation);
		if (klass.CallType == FuCallType.Sealed)
			Write("final ");
		OpenClass(klass, "", " : ");
		if (NeedsConstructor(klass)) {
			if (klass.Constructor != null) {
				WriteDoc(klass.Constructor.Documentation);
				WriteVisibility(klass.Constructor.Visibility);
			}
			else
				Write("private ");
			WriteLine("this()");
			OpenBlock();
			WriteConstructorBody(klass);
			CloseBlock();
		}
		else if (klass.Id == FuId.ExceptionClass) {
			Include("std.exception");
			WriteLine("mixin basicExceptionCtors;");
		}
		WriteMembers(klass, false);
		CloseBlock();
	}

	protected override void WriteCoercedInternal!(FuType type, FuExpr expr, FuPriority parent)
	{
		if (type is FuRangeType) // FIXME: had a condition that always holds: && (expr.Type is FuIntegerType || (expr.Type is FuRangeType range && !type.IsAssignableFrom(range)))
			WriteStaticCast(type, expr);
		else if (type is FuFloatingType && !(expr.Type is FuFloatingType))
			WriteStaticCast(type, expr);
		else if (type is FuClassType && !(type is FuArrayStorageType) && expr.Type is FuArrayStorageType) { // TODO: works without this?
			base.WriteCoercedInternal(type, expr, FuPriority.Primary);
			Write("[]");
		}
		else
			base.WriteCoercedInternal(type, expr, parent);
	}

	void WriteResources!(SortedDictionary<string(), List<byte>()> resources)
	{
		WriteNewLine();
		WriteLine("private static struct FuResource");
		OpenBlock();
		foreach ((string name, List<byte> content) in resources) {
			Write("private static immutable ubyte[] ");
			WriteResourceName(name);
			WriteLine(" = [");
			WriteChar('\t');
			WriteBytes(content);
			WriteLine(" ];");
		}
		CloseBlock();
	}

	void WriteMain!(FuMethod main, string namespace)
	{
		WriteNewLine();
		WriteType(main.Type, true);
		if (main.Parameters.Count() == 1) {
			Write(" main(string[] args) => ");
			WriteName(main.Parent);
			WriteLine(".main(args[1 .. $]);");
		}
		else {
			Write(" main() => ");
			if (namespace.Length != 0) {
				Write(namespace);
				WriteChar('.');
			}
			WriteName(main.Parent);
			WriteLine(".main();");
		}
	}

	public override void WriteProgram!(FuProgram program, string outputFile, string namespace)
	{
		this.HasListInsert = false;
		this.HasListRemoveAt = false;
		this.HasQueueDequeue = false;
		this.HasStackPop = false;
		this.HasSortedDictionaryInsert = false;
		this.HasSortedDictionaryFind = false;
		this.BitConverter = false;

		OpenStringWriter();
		if (namespace.Length != 0) {
			Write("struct ");
			WriteLine(namespace);
			OpenBlock();
			WriteLine("static:");
		}
		WriteTopLevelNatives(program);
		WriteTypes(program);
		if (program.Resources.Count > 0)
			WriteResources(program.Resources);
		if (namespace.Length != 0)
			CloseBlock();

		CreateFile(null, outputFile);
		if (this.HasListInsert || this.HasListRemoveAt || this.HasStackPop)
			Include("std.container.array");
		if (this.HasSortedDictionaryInsert) {
			Include("std.container.rbtree");
			Include("std.typecons");
		}
		WriteIncludes("import ", ";");
		if (this.HasListInsert) {
			WriteNewLine();
			WriteLine("private void insertInPlace(T, U...)(Array!T* arr, size_t pos, auto ref U stuff)");
			OpenBlock();
			WriteLine("arr.insertAfter((*arr)[0 .. pos], stuff);");
			CloseBlock();
		}
		if (this.HasListRemoveAt) {
			WriteNewLine();
			WriteLine("private void removeAt(T)(Array!T* arr, size_t pos, size_t count = 1)");
			OpenBlock();
			WriteLine("arr.linearRemove((*arr)[pos .. pos + count]);");
			CloseBlock();
		}
		if (this.HasQueueDequeue) {
			WriteNewLine();
			WriteLine("private T dequeue(T)(ref DList!T q)");
			OpenBlock();
			WriteLine("scope(exit) q.removeFront(); return q.front;");
			CloseBlock();
		}
		if (this.HasStackPop) {
			WriteNewLine();
			WriteLine("private T pop(T)(ref Array!T stack)");
			OpenBlock();
			WriteLine("scope(exit) stack.removeBack(); return stack.back;");
			CloseBlock();
		}
		if (this.HasSortedDictionaryFind) {
			WriteNewLine();
			WriteLine("private U find(T, U)(RedBlackTree!(Tuple!(T, U), \"a[0] < b[0]\") dict, T key)");
			OpenBlock();
			WriteLine("return dict.equalRange(tuple(key, U.init)).front[1];");
			CloseBlock();
		}
		if (this.HasSortedDictionaryInsert) {
			WriteNewLine();
			WriteLine("private void replace(T, U)(RedBlackTree!(Tuple!(T, U), \"a[0] < b[0]\") dict, T key, lazy U value)");
			OpenBlock();
			WriteLine("dict.removeKey(tuple(key, U.init));");
			WriteLine("dict.insert(tuple(key, value));");
			CloseBlock();
		}
		if (this.BitConverter) {
			WriteNewLine();
			WriteLine("private T fuBitCast(T, S)(S value)");
			OpenBlock();
			WriteLine("union U { S s; T t; };");
			WriteLine("U u = U(value);");
			WriteLine("return u.t;");
			CloseBlock();
		}
		CloseStringWriter();
		if (program.Main != null)
			WriteMain(program.Main, namespace);
		CloseFile();
	}
}