// 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();
}
}