// GenPy.fu - Python code generator
//
// Copyright (C) 2020-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 GenPy : GenPySwift
{
bool ChildPass;
bool TimeNs;
bool SwitchBreak;
protected override string GetTargetName() => "Python";
protected override void WriteBanner!()
{
WriteLine("# Generated automatically with \"fut\". Do not edit.");
}
protected override void StartDocLine!()
{
}
protected override void WriteDocCode!(string s)
{
switch (s) {
case "true":
Write("True");
break;
case "false":
Write("False");
break;
case "null":
Write("None");
break;
default:
Write(s);
break;
}
}
protected override string GetDocBullet() => " * ";
void StartDoc!(FuCodeDoc doc)
{
Write("\"\"\"");
WriteDocPara(doc.Summary, false);
if (doc.Details.Count > 0) {
WriteNewLine();
foreach (FuDocBlock block in doc.Details) {
WriteNewLine();
WriteDocBlock(block, false);
}
}
}
protected override void WriteDoc!(FuCodeDoc? doc)
{
if (doc != null) {
StartDoc(doc);
WriteLine("\"\"\"");
}
}
protected override void WriteParameterDoc!(FuVar param, bool first)
{
if (first) {
WriteNewLine();
WriteNewLine();
}
Write(":param ");
WriteName(param);
Write(": ");
WriteDocPara(param.Documentation.Summary, false);
WriteNewLine();
}
protected override void WriteThrowsDoc!(FuThrowsDeclaration decl)
{
Write(":raises ");
WriteExceptionClass(decl.Symbol);
Write(": ");
WriteDocPara(decl.Documentation.Summary, false);
WriteNewLine();
}
void WritePyDoc!(FuMethod method)
{
if (method.Documentation == null)
return;
StartDoc(method.Documentation);
WriteParametersAndThrowsDoc(method);
WriteLine("\"\"\"");
}
internal override void VisitLiteralNull!()
{
Write("None");
}
internal override void VisitLiteralFalse!()
{
Write("False");
}
internal override void VisitLiteralTrue!()
{
Write("True");
}
void WriteNameNotKeyword!(string name)
{
switch (name) {
case "this":
Write("self");
break;
case "And":
case "Array":
case "As":
case "Assert":
case "Async":
case "Await":
case "Bool":
case "Break":
case "Class":
case "Continue":
case "Def":
case "Del":
case "Dict":
case "Elif":
case "Else":
case "Enum":
case "Except":
case "Finally":
case "For":
case "From":
case "Global":
case "If":
case "Import":
case "In":
case "Is":
case "Lambda":
case "Len":
case "List":
case "Math":
case "Nonlocal":
case "Not":
case "Or":
case "Pass":
case "Raise":
case "Re":
case "Return":
case "Str":
case "Sys":
case "Try":
case "While":
case "With":
case "Yield":
case "and":
case "array":
case "as":
case "async":
case "await":
case "def":
case "del":
case "dict":
case "elif":
case "enum":
case "except":
case "finally":
case "from":
case "global":
case "import":
case "is":
case "json":
case "lambda":
case "len":
case "list":
case "math":
case "nonlocal":
case "not":
case "or":
case "pass":
case "raise":
case "re":
case "str":
case "sys":
case "try":
case "with":
case "yield":
WriteCamelCase(name);
WriteChar('_');
break;
default:
WriteLowercaseWithUnderscores(name);
break;
}
}
protected override void WriteName!(FuSymbol symbol)
{
switch (symbol) {
case FuContainerType container:
if (!container.IsPublic)
WriteChar('_');
Write(symbol.Name);
break;
case FuConst konst:
if (konst.Visibility != FuVisibility.Public)
WriteChar('_');
WriteUppercaseConstName(konst);
break;
case FuVar:
WriteNameNotKeyword(symbol.Name);
break;
case FuMember member:
if (member.Id == FuId.ClassToString)
Write("__str__");
else if (member.Visibility == FuVisibility.Public)
WriteNameNotKeyword(symbol.Name);
else {
WriteChar('_');
WriteLowercaseWithUnderscores(symbol.Name);
}
break;
default:
assert false;
}
}
void WritePyClassAnnotation!(FuContainerType type)
{
if (this.WrittenTypes.Contains(type))
WriteName(type);
else {
WriteChar('"');
WriteName(type);
WriteChar('"');
}
}
void WriteCollectionTypeAnnotation!(string name, FuClassType klass)
{
Write(name);
WriteChar('[');
WriteTypeAnnotation(klass.GetElementType(), klass.Class.Id == FuId.ArrayStorageClass);
if (klass.Class.TypeParameterCount == 2) {
Write(", ");
WriteTypeAnnotation(klass.GetValueType());
}
WriteChar(']');
}
void WriteTypeAnnotation!(FuType type, bool nullable = false)
{
switch (type) {
case FuIntegerType:
Write("int");
break;
case FuFloatingType:
Write("float");
break;
case FuEnum enu:
if (enu.Id == FuId.BoolType)
Write("bool");
else
WritePyClassAnnotation(enu);
break;
case FuClassType klass:
nullable = nullable ? !(klass is FuStorageType) : klass.Nullable;
switch (klass.Class.Id) {
case FuId.None:
case FuId.ExceptionClass:
if (nullable && !this.WrittenTypes.Contains(klass.Class)) {
WriteChar('"');
WriteName(klass.Class);
Write(" | None\"");
return;
}
WritePyClassAnnotation(klass.Class);
break;
case FuId.StringClass:
Write("str");
nullable = klass.Nullable;
break;
case FuId.ArrayPtrClass:
case FuId.ArrayStorageClass:
case FuId.ListClass:
case FuId.StackClass:
if (!(klass.GetElementType() is FuNumericType number))
WriteCollectionTypeAnnotation("list", klass);
else if (number.Id == FuId.ByteRange) {
Write("bytearray");
if (klass.Class.Id == FuId.ArrayPtrClass && !(klass is FuReadWriteClassType))
Write(" | bytes");
}
else {
Include("array");
Write("array.array");
}
break;
case FuId.QueueClass:
Include("collections");
WriteCollectionTypeAnnotation("collections.deque", klass);
break;
case FuId.PriorityQueueClass:
Write("list[tuple[");
WriteTypeAnnotation(klass.TypeArg1);
Write(", ");
WriteTypeAnnotation(klass.TypeArg0);
Write("]]");
break;
case FuId.HashSetClass:
case FuId.SortedSetClass:
WriteCollectionTypeAnnotation("set", klass);
break;
case FuId.DictionaryClass:
case FuId.SortedDictionaryClass:
case FuId.OrderedDictionaryClass:
WriteCollectionTypeAnnotation("dict", klass);
break;
case FuId.TextWriterClass:
Include("io");
Write("io.TextIOBase");
break;
case FuId.StringWriterClass:
Include("io");
Write("io.StringIO");
break;
case FuId.RegexClass:
Include("re");
Write("re.Pattern");
break;
case FuId.MatchClass:
Include("re");
Write("re.Match");
break;
case FuId.JsonElementClass:
Write("dict | list | str | float | bool | None");
break;
case FuId.LockClass:
Include("threading");
Write("threading.RLock");
break;
default:
assert false;
}
if (nullable)
Write(" | None");
break;
default:
assert false;
}
}
protected override void WriteTypeAndName!(FuNamedValue value)
{
WriteName(value);
Write(": ");
WriteTypeAnnotation(value.Type);
}
protected override void WriteLocalName!(FuSymbol symbol, FuPriority parent)
{
if (symbol.Parent is FuForeach forEach && forEach.Collection.Type is FuStringType) {
Write("ord(");
WriteNameNotKeyword(symbol.Name);
WriteChar(')');
}
else
base.WriteLocalName(symbol, parent);
}
static int GetArrayCode(FuType type)
{
switch (type.Id) {
case FuId.SByteRange:
return 'b';
case FuId.ByteRange:
return 'B';
case FuId.ShortRange:
return 'h';
case FuId.UShortRange:
return 'H';
case FuId.IntType:
return 'i';
case FuId.NIntType:
case FuId.LongType:
return 'q';
case FuId.FloatType:
return 'f';
case FuId.DoubleType:
return 'd';
default:
assert false;
}
}
internal override void VisitAggregateInitializer!(FuAggregateInitializer expr)
{
assert expr.Type is FuArrayStorageType array;
if (array.GetElementType() is FuNumericType number) {
int c = GetArrayCode(number);
if (c == 'B')
Write("bytes(");
else {
Include("array");
Write("array.array(\"");
WriteChar(c);
Write("\", ");
}
base.VisitAggregateInitializer(expr);
WriteChar(')');
}
else
base.VisitAggregateInitializer(expr);
}
internal override void VisitInterpolatedString!(FuInterpolatedString expr, FuPriority parent)
{
Write("f\"");
foreach (FuInterpolatedPart part in expr.Parts) {
WriteDoubling(part.Prefix, '{');
WriteChar('{');
part.Argument.Accept(this, FuPriority.Argument);
WritePyFormat(part);
}
WriteDoubling(expr.Suffix, '{');
WriteChar('"');
}
internal override void VisitPrefixExpr!(FuPrefixExpr expr, FuPriority parent)
{
if (expr.Op == FuToken.ExclamationMark) {
if (parent > FuPriority.CondAnd)
WriteChar('(');
Write("not ");
expr.Inner.Accept(this, FuPriority.Or);
if (parent > FuPriority.CondAnd)
WriteChar(')');
}
else
base.VisitPrefixExpr(expr, parent);
}
protected override string GetReferenceEqOp(bool not) => not ? " is not " : " is ";
protected override void WriteCharAt!(FuBinaryExpr expr)
{
Write("ord(");
WriteIndexingExpr(expr, FuPriority.Argument);
WriteChar(')');
}
protected override void WriteStringLength!(FuExpr expr)
{
WriteCall("len", expr);
}
protected override void WriteArrayLength!(FuExpr expr, FuPriority parent)
{
WriteCall("len", expr);
}
internal override void VisitSymbolReference!(FuSymbolReference expr, FuPriority parent)
{
switch (expr.Symbol.Id) {
case FuId.ConsoleError:
Include("sys");
Write("sys.stderr");
break;
case FuId.ConsoleOut:
Include("sys");
Write("sys.stdout");
break;
case FuId.ListCount:
case FuId.QueueCount:
case FuId.StackCount:
case FuId.PriorityQueueCount:
case FuId.HashSetCount:
case FuId.SortedSetCount:
case FuId.DictionaryCount:
case FuId.SortedDictionaryCount:
case FuId.OrderedDictionaryCount:
WriteStringLength(expr.Left);
break;
case FuId.MathNaN:
Include("math");
Write("math.nan");
break;
case FuId.MathNegativeInfinity:
Include("math");
Write("-math.inf");
break;
case FuId.MathPositiveInfinity:
Include("math");
Write("math.inf");
break;
default:
if (!WriteJavaMatchProperty(expr, parent))
base.VisitSymbolReference(expr, parent);
break;
}
}
internal override void VisitBinaryExpr!(FuBinaryExpr expr, FuPriority parent)
{
switch (expr.Op) {
case FuToken.Slash:
if (expr.Type is FuIntegerType) {
bool floorDiv;
if (expr.Left is FuRangeType leftRange && leftRange.Min >= 0
&& expr.Right is FuRangeType rightRange && rightRange.Min >= 0) {
if (parent > FuPriority.Or)
WriteChar('(');
floorDiv = true;
}
else {
Write("int(");
floorDiv = false;
}
expr.Left.Accept(this, FuPriority.Mul);
Write(floorDiv ? " // " : " / ");
expr.Right.Accept(this, FuPriority.Primary);
if (!floorDiv || parent > FuPriority.Or)
WriteChar(')');
}
else
base.VisitBinaryExpr(expr, parent);
break;
case FuToken.CondAnd:
WriteBinaryExpr(expr, parent > FuPriority.CondAnd || parent == FuPriority.CondOr, FuPriority.CondAnd, " and ", FuPriority.CondAnd);
break;
case FuToken.CondOr:
WriteBinaryExpr2(expr, parent, FuPriority.CondOr, " or ");
break;
case FuToken.Assign:
if (this.AtLineStart) {
for (FuExpr right = expr.Right; right is FuBinaryExpr rightBinary && rightBinary.HasSideEffect(); right = rightBinary.Right) {
if (rightBinary.Op != FuToken.Assign) {
VisitBinaryExpr(rightBinary, FuPriority.Statement);
WriteNewLine();
break;
}
}
}
expr.Left.Accept(this, FuPriority.Assign);
Write(" = ");
{
(expr.Right is FuBinaryExpr rightBinary && rightBinary.HasSideEffect() && rightBinary.Op != FuToken.Assign ? rightBinary.Left /* FIXME: side effect*/ : expr.Right).Accept(this, FuPriority.Assign);
}
break;
case FuToken.AddAssign:
case FuToken.SubAssign:
case FuToken.MulAssign:
case FuToken.DivAssign:
case FuToken.ModAssign:
case FuToken.AndAssign:
case FuToken.OrAssign:
case FuToken.XorAssign:
case FuToken.ShiftLeftAssign:
case FuToken.ShiftRightAssign:
{
FuExpr right = expr.Right;
if (right is FuBinaryExpr rightBinary && rightBinary.HasSideEffect()) {
VisitBinaryExpr(rightBinary, FuPriority.Statement);
WriteNewLine();
right = rightBinary.Left; // FIXME: side effect
}
expr.Left.Accept(this, FuPriority.Assign);
WriteChar(' ');
if (expr.Op == FuToken.DivAssign && expr.Type is FuIntegerType)
WriteChar('/');
Write(expr.GetOpString());
WriteChar(' ');
right.Accept(this, FuPriority.Argument);
}
break;
case FuToken.Is:
Write("isinstance(");
FuSymbol klass;
switch (expr.Right) {
case FuSymbolReference symbol:
klass = symbol.Symbol;
break;
case FuVar def:
WriteName(def);
Write(" := ");
klass = def.Type.AsClassType().Class;
break;
default:
assert false;
}
expr.Left.Accept(this, FuPriority.Argument);
Write(", ");
WriteName(klass);
WriteChar(')');
break;
default:
base.VisitBinaryExpr(expr, parent);
break;
}
}
protected override void WriteCoercedSelect!(FuType type, FuSelectExpr expr, FuPriority parent)
{
if (parent > FuPriority.Select)
WriteChar('(');
WriteCoerced(type, expr.OnTrue, FuPriority.Select);
Write(" if ");
expr.Cond.Accept(this, FuPriority.SelectCond);
Write(" else ");
WriteCoerced(type, expr.OnFalse, FuPriority.Select);
if (parent > FuPriority.Select)
WriteChar(')');
}
void WriteDefaultValue!(FuType type)
{
if (type is FuNumericType)
WriteChar('0');
else if (type is FuEnum enu) {
if (type.Id == FuId.BoolType)
VisitLiteralFalse();
else {
WriteName(enu);
WriteChar('.');
WriteUppercaseWithUnderscores(enu.GetFirstValue().Name);
}
}
else if ((type.Id == FuId.StringPtrType && !type.Nullable) || type.Id == FuId.StringStorageType)
Write("\"\"");
else
Write("None");
}
void WritePyNewArray!(FuType elementType, FuExpr? value, FuExpr lengthExpr)
{
switch (elementType) {
case FuStorageType:
Write("[ ");
WriteNewStorage(elementType);
Write(" for _ in range(");
lengthExpr.Accept(this, FuPriority.Argument);
Write(") ]");
break;
case FuNumericType:
int c = GetArrayCode(elementType);
if (c == 'B' && (value == null || value.IsLiteralZero()))
WriteCall("bytearray", lengthExpr);
else {
Include("array");
Write("array.array(\"");
WriteChar(c);
Write("\", [ ");
if (value == null)
WriteChar('0');
else
value.Accept(this, FuPriority.Argument);
Write(" ]) * ");
lengthExpr.Accept(this, FuPriority.Mul);
}
break;
default:
Write("[ ");
if (value == null)
WriteDefaultValue(elementType);
else
value.Accept(this, FuPriority.Argument);
Write(" ] * ");
lengthExpr.Accept(this, FuPriority.Mul);
break;
}
}
protected override void WriteNewArray!(FuType elementType, FuExpr lengthExpr, FuPriority parent)
{
WritePyNewArray(elementType, null, lengthExpr);
}
protected override void WriteArrayStorageInit!(FuArrayStorageType array, FuExpr? value)
{
Write(" = ");
WritePyNewArray(array.GetElementType(), null, array.LengthExpr);
}
protected override void WriteNew!(FuReadWriteClassType klass, FuPriority parent)
{
switch (klass.Class.Id) {
case FuId.ListClass:
case FuId.StackClass:
if (klass.GetElementType() is FuNumericType number) {
int c = GetArrayCode(number);
if (c == 'B')
Write("bytearray()");
else {
Include("array");
Write("array.array(\"");
WriteChar(c);
Write("\")");
}
}
else
Write("[]");
break;
case FuId.QueueClass:
Include("collections");
Write("collections.deque()");
break;
case FuId.PriorityQueueClass:
Write("[]");
break;
case FuId.HashSetClass:
case FuId.SortedSetClass:
Write("set()");
break;
case FuId.DictionaryClass:
case FuId.SortedDictionaryClass:
case FuId.OrderedDictionaryClass:
Write("{}");
break;
case FuId.StringWriterClass:
Include("io");
Write("io.StringIO()");
break;
case FuId.LockClass:
Include("threading");
Write("threading.RLock()");
break;
default:
WriteName(klass.Class);
Write("()");
break;
}
}
void WriteSlice!(FuExpr startIndex, FuExpr? length)
{
WriteChar('[');
startIndex.Accept(this, FuPriority.Argument);
WriteChar(':');
if (length != null)
WriteAdd(startIndex, length); // FIXME: side effect
WriteChar(']');
}
void WriteAssignSorted!(FuExpr obj, string byteArray)
{
Write(" = ");
int c = GetArrayCode(obj.Type.AsClassType().GetElementType());
if (c == 'B') {
Write(byteArray);
WriteChar('(');
}
else {
Include("array");
Write("array.array(\"");
WriteChar(c);
Write("\", ");
}
Write("sorted(");
}
void WriteAllAny!(string function, FuExpr obj, List<FuExpr#> args)
{
Write(function);
WriteChar('(');
assert args[0] is FuLambdaExpr lambda;
lambda.Body.Accept(this, FuPriority.Argument);
Write(" for ");
WriteName(lambda.First);
Write(" in ");
obj.Accept(this, FuPriority.Argument);
WriteChar(')');
}
void WriteBitConverterIntFloat!(int from, int to, FuExpr arg)
{
Include("struct");
Write("struct.unpack(\"");
WriteChar(to);
Write("\", struct.pack(\"");
WriteChar(from);
Write("\", ");
arg.Accept(this, FuPriority.Argument);
Write("))[0]");
}
void WritePyRegexOptions!(List<FuExpr#> args)
{
Include("re");
WriteRegexOptions(args, ", ", " | ", "", "re.I", "re.M", "re.S");
}
void WriteRegexSearch!(List<FuExpr#> args)
{
Write("re.search(");
args[1].Accept(this, FuPriority.Argument);
Write(", ");
args[0].Accept(this, FuPriority.Argument);
WritePyRegexOptions(args);
WriteChar(')');
}
void WriteJsonElementIs!(FuExpr obj, string name)
{
Write("isinstance(");
obj.Accept(this, FuPriority.Argument);
Write(", ");
Write(name);
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.StringReplace:
case FuId.QueueClear:
case FuId.StackPop:
case FuId.HashSetAdd:
case FuId.HashSetClear:
case FuId.HashSetRemove:
case FuId.SortedSetAdd:
case FuId.SortedSetClear:
case FuId.SortedSetRemove:
case FuId.DictionaryClear:
case FuId.SortedDictionaryClear:
case FuId.OrderedDictionaryClear:
case FuId.TextWriterFlush:
if (obj == null)
WriteLocalName(method, FuPriority.Primary);
else if (IsReferenceTo(obj, FuId.BasePtr)) {
WriteName(method.Parent);
WriteChar('.');
WriteName(method);
Write("(self");
if (args.Count > 0) {
Write(", ");
WriteCoercedArgs(method, args);
}
WriteChar(')');
break;
}
else {
obj.Accept(this, FuPriority.Primary);
WriteChar('.');
WriteName(method);
}
WriteCoercedArgsInParentheses(method, args);
break;
case FuId.EnumFromInt:
WriteName(type);
WriteInParentheses(args);
break;
case FuId.EnumToInt:
WritePostfix(obj, ".value");
break;
case FuId.EnumHasFlag:
case FuId.StringContains:
case FuId.ArrayContains:
case FuId.ListContains:
case FuId.HashSetContains:
case FuId.SortedSetContains:
case FuId.DictionaryContainsKey:
case FuId.SortedDictionaryContainsKey:
case FuId.OrderedDictionaryContainsKey:
WriteContains(obj, args[0], parent);
break;
case FuId.StringEndsWith:
WriteMethodCall(obj, "endswith", args[0]);
break;
case FuId.StringIndexOf:
WriteMethodCall(obj, "find", args[0]);
break;
case FuId.StringLastIndexOf:
WriteMethodCall(obj, "rfind", args[0]);
break;
case FuId.StringStartsWith:
WriteMethodCall(obj, "startswith", args[0]);
break;
case FuId.StringSubstring:
obj.Accept(this, FuPriority.Primary);
WriteSlice(args[0], args.Count == 2 ? args[1] : null);
break;
case FuId.StringToLower:
WritePostfix(obj, ".lower()");
break;
case FuId.StringToUpper:
WritePostfix(obj, ".upper()");
break;
case FuId.ArrayBinarySearchAll:
Include("bisect");
WriteCall("bisect.bisect_left", obj, args[0]);
break;
case FuId.ArrayBinarySearchPart:
Include("bisect");
Write("bisect.bisect_left(");
obj.Accept(this, FuPriority.Argument);
Write(", ");
args[0].Accept(this, FuPriority.Argument);
Write(", ");
args[1].Accept(this, FuPriority.Argument);
Write(", ");
args[2].Accept(this, FuPriority.Argument);
WriteChar(')');
break;
case FuId.ArrayCopyTo:
case FuId.ListCopyTo:
args[1].Accept(this, FuPriority.Primary);
WriteSlice(args[2], args[3]);
Write(" = ");
obj.Accept(this, FuPriority.Primary);
WriteSlice(args[0], args[3]);
break;
case FuId.ArrayFillAll:
case FuId.ArrayFillPart:
obj.Accept(this, FuPriority.Primary);
if (args.Count == 1) {
Write("[:] = ");
assert obj.Type is FuArrayStorageType array;
WritePyNewArray(array.GetElementType(), args[0], array.LengthExpr);
}
else {
WriteSlice(args[1], args[2]);
Write(" = ");
WritePyNewArray(obj.Type.AsClassType().GetElementType(), args[0], args[2]); // FIXME: side effect
}
break;
case FuId.ArraySortAll:
case FuId.ListSortAll:
obj.Accept(this, FuPriority.Assign);
WriteAssignSorted(obj, "bytearray");
obj.Accept(this, FuPriority.Argument);
Write("))");
break;
case FuId.ArraySortPart:
case FuId.ListSortPart:
obj.Accept(this, FuPriority.Primary);
WriteSlice(args[0], args[1]);
WriteAssignSorted(obj, "bytes");
obj.Accept(this, FuPriority.Primary);
WriteSlice(args[0], args[1]);
Write("))");
break;
case FuId.ListAdd:
WriteListAdd(obj, "append", args);
break;
case FuId.ListAddRange:
if (obj is FuSymbolReference) {
obj.Accept(this, FuPriority.Assign);
Write(" += ");
args[0].Accept(this, FuPriority.Argument);
}
else
WriteMethodCall(obj, "extend", args[0]);
break;
case FuId.ListAll:
WriteAllAny("all", obj, args);
break;
case FuId.ListAny:
WriteAllAny("any", obj, args);
break;
case FuId.ListClear:
case FuId.StackClear:
if (obj.Type.AsClassType().GetElementType() is FuNumericType number && GetArrayCode(number) != 'B') { // TODO: Python 3.13 adds array.clear
Write("del ");
WritePostfix(obj, "[:]");
}
else
WritePostfix(obj, ".clear()");
break;
case FuId.ListIndexOf:
if (parent > FuPriority.Select)
WriteChar('(');
WriteMethodCall(obj, "index", args[0]);
Write(" if ");
WriteContains(obj, args[0], FuPriority.Argument); // FIXME: side effects
Write(" else -1");
if (parent > FuPriority.Select)
WriteChar(')');
break;
case FuId.ListInsert:
WriteListInsert(obj, "insert", args);
break;
case FuId.ListLast:
case FuId.StackPeek:
WritePostfix(obj, "[-1]");
break;
case FuId.ListRemoveAt:
Write("del ");
WriteIndexing(obj, args[0]);
break;
case FuId.DictionaryRemove:
case FuId.SortedDictionaryRemove:
case FuId.OrderedDictionaryRemove:
WritePostfix(obj, ".pop(");
args[0].Accept(this, FuPriority.Argument);
Write(", None)");
break;
case FuId.ListRemoveRange:
Write("del ");
obj.Accept(this, FuPriority.Primary);
WriteSlice(args[0], args[1]);
break;
case FuId.QueueDequeue:
WritePostfix(obj, ".popleft()");
break;
case FuId.QueueEnqueue:
case FuId.StackPush:
WriteListAppend(obj, args);
break;
case FuId.QueuePeek:
WritePostfix(obj, "[0]");
break;
case FuId.PriorityQueueClear:
WritePostfix(obj, ".clear()");
break;
case FuId.PriorityQueueDequeue:
Include("heapq");
Write("heapq.heappop(");
obj.Accept(this, FuPriority.Argument);
Write(")[1]");
break;
case FuId.PriorityQueueEnqueue:
Include("heapq");
Write("heapq.heappush(");
obj.Accept(this, FuPriority.Argument);
Write(", (");
args[1].Accept(this, FuPriority.Argument);
Write(", ");
args[0].Accept(this, FuPriority.Argument);
Write("))");
break;
case FuId.PriorityQueuePeek:
WritePostfix(obj, "[0][1]");
break;
case FuId.DictionaryAdd:
WriteDictionaryAdd(obj, args);
break;
case FuId.TextWriterWrite:
Write("print(");
args[0].Accept(this, FuPriority.Argument);
Write(", end=\"\", file=");
obj.Accept(this, FuPriority.Argument);
WriteChar(')');
break;
case FuId.TextWriterWriteChar:
case FuId.TextWriterWriteCodePoint:
WriteMethodCall(obj, "write(chr", args[0]);
WriteChar(')');
break;
case FuId.TextWriterWriteLine:
Write("print(");
if (args.Count == 1) {
args[0].Accept(this, FuPriority.Argument);
Write(", ");
}
Write("file=");
obj.Accept(this, FuPriority.Argument);
WriteChar(')');
break;
case FuId.ConsoleReadLine:
Write("input()");
break;
case FuId.ConsoleWrite:
Write("print(");
args[0].Accept(this, FuPriority.Argument);
Write(", end=\"\")");
break;
case FuId.ConsoleWriteLine:
Write("print(");
if (args.Count == 1)
args[0].Accept(this, FuPriority.Argument);
WriteChar(')');
break;
case FuId.StringWriterClear:
WritePostfix(obj, ".seek(0)");
WriteNewLine();
WritePostfix(obj, ".truncate(0)"); // FIXME: side effect
break;
case FuId.StringWriterToString:
WritePostfix(obj, ".getvalue()");
break;
case FuId.BitConverterInt32BitsToSingle:
WriteBitConverterIntFloat('i', 'f', args[0]);
break;
case FuId.BitConverterInt64BitsToDouble:
WriteBitConverterIntFloat('q', 'd', args[0]);
break;
case FuId.BitConverterSingleToInt32Bits:
WriteBitConverterIntFloat('f', 'i', args[0]);
break;
case FuId.BitConverterDoubleToInt64Bits:
WriteBitConverterIntFloat('d', 'q', args[0]);
break;
case FuId.ConvertToBase64String:
Include("base64");
Write("base64.b64encode(");
args[0].Accept(this, FuPriority.Primary);
WriteSlice(args[1], args[2]);
Write(").decode(\"utf8\")");
break;
case FuId.UTF8GetByteCount:
Write("len(");
WritePostfix(args[0], ".encode(\"utf8\"))");
break;
case FuId.UTF8GetBytes:
Write("fubytes = ");
args[0].Accept(this, FuPriority.Primary);
WriteLine(".encode(\"utf8\")");
args[1].Accept(this, FuPriority.Primary);
WriteChar('[');
args[2].Accept(this, FuPriority.Argument);
WriteChar(':');
StartAdd(args[2]); // FIXME: side effect
WriteLine("len(fubytes)] = fubytes");
break;
case FuId.UTF8GetString:
args[0].Accept(this, FuPriority.Primary);
WriteSlice(args[1], args[2]);
Write(".decode(\"utf8\")");
break;
case FuId.EnvironmentGetEnvironmentVariable:
Include("os");
WriteCall("os.getenv", args[0]);
break;
case FuId.DateTimeOffsetUtcNowToUnixTimeMilliseconds:
this.TimeNs = true;
if (parent > FuPriority.Mul)
WriteChar('(');
Write("time_ns() // 1000000");
if (parent > FuPriority.Mul)
WriteChar(')');
break;
case FuId.RegexCompile:
Write("re.compile(");
args[0].Accept(this, FuPriority.Argument);
WritePyRegexOptions(args);
WriteChar(')');
break;
case FuId.RegexEscape:
Include("re");
WriteCall("re.escape", args[0]);
break;
case FuId.RegexIsMatchStr:
if (parent > FuPriority.Equality)
WriteChar('(');
WriteRegexSearch(args);
Write(" is not None");
if (parent > FuPriority.Equality)
WriteChar(')');
break;
case FuId.RegexIsMatchRegex:
if (parent > FuPriority.Equality)
WriteChar('(');
WriteMethodCall(obj, "search", args[0]);
Write(" is not None");
if (parent > FuPriority.Equality)
WriteChar(')');
break;
case FuId.MatchFindStr:
case FuId.MatchFindRegex:
if (parent > FuPriority.Equality)
WriteChar('(');
obj.Accept(this, FuPriority.Equality);
Write(" is not None");
if (parent > FuPriority.Equality)
WriteChar(')');
break;
case FuId.MatchGetCapture:
WriteMethodCall(obj, "group", args[0]);
break;
case FuId.JsonElementParse:
Include("json");
WriteCall("json.loads", args[0]);
break;
case FuId.JsonElementIsObject:
WriteJsonElementIs(obj, "dict");
break;
case FuId.JsonElementIsArray:
WriteJsonElementIs(obj, "list");
break;
case FuId.JsonElementIsString:
WriteJsonElementIs(obj, "str");
break;
case FuId.JsonElementIsNumber:
WriteJsonElementIs(obj, "float");
break;
case FuId.JsonElementIsBoolean:
WriteJsonElementIs(obj, "bool");
break;
case FuId.JsonElementIsNull:
if (parent > FuPriority.Equality)
WriteChar('(');
obj.Accept(this, FuPriority.Equality);
Write(" is None");
if (parent > FuPriority.Equality)
WriteChar(')');
break;
case FuId.JsonElementGetObject:
case FuId.JsonElementGetArray:
case FuId.JsonElementGetString:
case FuId.JsonElementGetDouble:
case FuId.JsonElementGetBoolean:
obj.Accept(this, parent);
break;
case FuId.MathMethod:
case FuId.MathIsFinite:
case FuId.MathIsNaN:
case FuId.MathLog2:
case FuId.MathSqrt:
Include("math");
Write("math.");
WriteLowercase(method.Name);
WriteInParentheses(args);
break;
case FuId.MathAbs:
WriteCall("abs", args[0]);
break;
case FuId.MathCeiling:
Include("math");
WriteCall("math.ceil", args[0]);
break;
case FuId.MathClamp:
Write("min(max(");
WriteClampAsMinMax(args);
break;
case FuId.MathFusedMultiplyAdd:
Include("math");
WriteCall("math.fma", args[0], args[1], args[2]);
break;
case FuId.MathIsInfinity:
Include("math");
WriteCall("math.isinf", args[0]);
break;
case FuId.MathMax:
WriteCall("max", args[0], args[1]);
break;
case FuId.MathMin:
WriteCall("min", args[0], args[1]);
break;
case FuId.MathRound:
WriteCall("round", args[0]);
break;
case FuId.MathTruncate:
Include("math");
WriteCall("math.trunc", args[0]);
break;
default:
NotSupported(obj, method.Name);
break;
}
}
protected override void WriteResource!(string name, int length)
{
Write("_FuResource.");
WriteResourceName(name);
}
protected override bool VisitPreCall!(FuCallExpr call)
{
switch (call.Method.Symbol.Id) {
case FuId.MatchFindStr:
call.Method.Left.Accept(this, FuPriority.Assign);
Write(" = ");
WriteRegexSearch(call.Arguments);
WriteNewLine();
return true;
case FuId.MatchFindRegex:
call.Method.Left.Accept(this, FuPriority.Assign);
Write(" = ");
WriteMethodCall(call.Arguments[1], "search", call.Arguments[0]);
WriteNewLine();
return true;
default:
return false;
}
}
protected override void StartTemporaryVar!(FuType type)
{
}
protected override void WriteTryParseStatement!(FuCallExpr call, FuStatement!? onParsed, FuStatement!? onFailure)
{
Write("try");
OpenChild();
call.Method.Left.Accept(this, FuPriority.Assign);
Write(" = ");
Write(call.Method.Symbol.Id == FuId.FloatTryParse || call.Method.Symbol.Id == FuId.DoubleTryParse ? "float" : "int");
WriteInParentheses(call.Arguments);
WriteNewLine();
if (onParsed != null)
FlattenBlock(onParsed);
CloseChild();
Write("except ValueError");
OpenChild();
WriteTryParseFailure(call, onFailure);
CloseChild();
}
protected override bool HasInitCode(FuNamedValue def) => def.Type is FuArrayStorageType
|| (def.Type.IsFinal() ? !(def.Value is FuLiteralNull) : def.Value != null);
internal override void VisitExpr!(FuExpr statement)
{
if (TryWriteTryParse(statement, null, null))
return;
if (!(statement is FuVar def) || HasInitCode(def)) {
WriteTemporaries(statement);
base.VisitExpr(statement);
}
}
protected override void StartLine!()
{
base.StartLine();
this.ChildPass = false;
}
protected override void OpenChild!()
{
WriteCharLine(':');
this.Indent++;
this.ChildPass = true;
}
protected override void CloseChild!()
{
if (this.ChildPass)
WriteLine("pass");
this.Indent--;
}
internal override void VisitLambdaExpr!(FuLambdaExpr expr)
{
assert false;
}
protected override void WriteAssertCast!(FuBinaryExpr expr)
{
assert expr.Right is FuVar def;
WriteTypeAndName(def);
Write(" = ");
expr.Left.Accept(this, FuPriority.Argument);
WriteNewLine();
}
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);
}
WriteNewLine();
}
internal override void VisitBreak!(FuBreak statement)
{
WriteLine(statement.LoopOrSwitch is FuSwitch ? "raise _CiBreak()" : "break");
}
protected override string GetIfNot() => "if not ";
void WriteInclusiveLimit!(FuExpr limit, int increment, string incrementString)
{
if (limit is FuLiteralLong literal)
VisitLiteralLong(literal.Value + increment, FuPriority.Argument);
else {
limit.Accept(this, FuPriority.Add);
Write(incrementString);
}
}
protected override void WriteForRange!(FuVar indVar, FuBinaryExpr cond, long rangeStep)
{
Write("range(");
if (rangeStep != 1 || !indVar.Value.IsLiteralZero()) {
indVar.Value.Accept(this, FuPriority.Argument);
Write(", ");
}
switch (cond.Op) {
case FuToken.Less:
case FuToken.Greater:
cond.Right.Accept(this, FuPriority.Argument);
break;
case FuToken.LessOrEqual:
WriteInclusiveLimit(cond.Right, 1, " + 1");
break;
case FuToken.GreaterOrEqual:
WriteInclusiveLimit(cond.Right, -1, " - 1");
break;
default:
assert false;
}
if (rangeStep != 1) {
Write(", ");
VisitLiteralLong(rangeStep, FuPriority.Argument);
}
WriteChar(')');
}
internal override void VisitForeach!(FuForeach statement)
{
Write("for ");
WriteName(statement.GetVar());
assert statement.Collection.Type is FuClassType klass;
if (klass.Class.TypeParameterCount == 2) {
Write(", ");
WriteName(statement.GetValueVar());
Write(" in ");
if (klass.Class.Id == FuId.SortedDictionaryClass) {
Write("sorted(");
WritePostfix(statement.Collection, ".items())");
}
else
WritePostfix(statement.Collection, ".items()");
}
else {
Write(" in ");
if (klass.Class.Id == FuId.SortedSetClass)
WriteCall("sorted", statement.Collection);
else
statement.Collection.Accept(this, FuPriority.Argument);
}
WriteChild(statement.Body);
}
protected override void WriteElseIf!()
{
Write("el");
}
internal override void VisitLock!(FuLock statement)
{
VisitXcrement(statement.Lock, false, true);
Write("with ");
statement.Lock.Accept(this, FuPriority.Argument);
OpenChild();
VisitXcrement(statement.Lock, true, true);
statement.Body.AcceptStatement(this);
CloseChild();
}
protected override void WriteResultVar!()
{
Write("result");
}
void WritePyCaseValue!(FuExpr value)
{
switch (value) {
case FuSymbolReference symbol when symbol.Symbol is FuClass klass:
WriteName(klass);
Write("()");
break;
case FuVar def:
WriteName(def.Type.AsClassType().Class);
Write("() as ");
WriteNameNotKeyword(def.Name);
break;
case FuBinaryExpr when1 when when1.Op == FuToken.When:
WritePyCaseValue(when1.Left);
Write(" if ");
when1.Right.Accept(this, FuPriority.Argument);
break;
default:
value.Accept(this, FuPriority.Or);
break;
}
}
void WritePyCaseBody!(FuSwitch statement, List<FuStatement#> body)
{
OpenChild();
VisitXcrement(statement.Value, true, true);
WriteFirstStatements(body, FuSwitch.LengthWithoutTrailingBreak(body));
CloseChild();
}
internal override void VisitSwitch!(FuSwitch statement)
{
bool earlyBreak = statement.Cases.Any(kase => FuSwitch.HasEarlyBreak(kase.Body))
|| FuSwitch.HasEarlyBreak(statement.DefaultBody);
if (earlyBreak) {
this.SwitchBreak = true;
Write("try");
OpenChild();
}
VisitXcrement(statement.Value, false, true);
Write("match ");
statement.Value.Accept(this, FuPriority.Argument);
OpenChild();
foreach (FuCase kase in statement.Cases) {
string op = "case ";
foreach (FuExpr caseValue in kase.Values) {
Write(op);
WritePyCaseValue(caseValue);
op = " | ";
}
WritePyCaseBody(statement, kase.Body);
}
if (statement.HasDefault()) {
Write("case _");
WritePyCaseBody(statement, statement.DefaultBody);
}
CloseChild();
if (earlyBreak) {
CloseChild();
Write("except _CiBreak");
OpenChild();
CloseChild();
}
}
internal override void VisitThrow!(FuThrow statement)
{
if (statement.Message != null)
VisitXcrement(statement.Message, false, true);
Write("raise ");
WriteThrowArgument(statement);
WriteNewLine();
// FIXME: VisitXcrement(statement.Message, true, true);
}
void WritePyClass!(FuContainerType type)
{
WriteNewLine();
Write("class ");
WriteName(type);
}
internal override void VisitEnumValue!(FuConst konst, FuConst? previous)
{
WriteUppercaseWithUnderscores(konst.Name);
Write(" = ");
VisitLiteralLong(konst.Value.IntValue(), FuPriority.Argument);
WriteNewLine();
WriteDoc(konst.Documentation);
}
protected override void WriteEnum!(FuEnum enu)
{
WritePyClass(enu);
Include("enum");
Write(enu is FuEnumFlags ? "(enum.Flag)" : "(enum.Enum)");
OpenChild();
WriteDoc(enu.Documentation);
enu.AcceptValues(this);
CloseChild();
this.WrittenTypes.Add(enu);
}
protected override void WriteConst!(FuConst konst)
{
if (konst.Visibility != FuVisibility.Private || konst.Type is FuArrayStorageType) {
WriteNewLine();
WriteName(konst);
Write(" = ");
konst.Value.Accept(this, FuPriority.Argument);
WriteNewLine();
WriteDoc(konst.Documentation);
}
}
protected override void WriteField!(FuField field)
{
WriteTypeAndName(field);
WriteNewLine();
}
protected override void WriteMethod!(FuMethod method)
{
WriteNewLine();
switch (method.CallType) {
case FuCallType.Static:
WriteLine("@staticmethod");
break;
case FuCallType.Abstract:
Include("abc");
WriteLine("@abc.abstractmethod");
break;
default:
break;
}
Write("def ");
WriteName(method);
if (method.CallType == FuCallType.Static)
WriteParameters(method, method.Id != FuId.Main);
else {
Write("(self");
WriteRemainingParameters(method, false, true);
}
Write(" -> ");
if (method.Type.Id == FuId.VoidType)
Write("None");
else
WriteTypeAnnotation(method.Type);
this.CurrentMethod = method;
OpenChild();
WritePyDoc(method);
if (method.Body != null)
method.Body.AcceptStatement(this);
CloseChild();
this.CurrentMethod = null;
}
bool InheritsConstructor(FuClass klass)
{
while (klass.Parent is FuClass baseClass) {
if (NeedsConstructor(baseClass))
return true;
klass = baseClass;
}
return false;
}
protected override void WriteInitField!(FuField field)
{
if (HasInitCode(field)) {
Write("self.");
WriteName(field);
WriteVarInit(field);
WriteNewLine();
WriteInitCode(field);
}
}
protected override void WriteClass!(FuClass klass, FuProgram program)
{
if (!WriteBaseClass(klass, program))
return;
this.WrittenTypes.Remove(klass);
WritePyClass(klass);
if (klass.Parent is FuClass baseClass) {
WriteChar('(');
WriteName(baseClass);
WriteChar(')');
}
else if (klass.CallType == FuCallType.Abstract) {
Include("abc");
Write("(abc.ABC)");
}
OpenChild();
WriteDoc(klass.Documentation);
if (NeedsConstructor(klass)) {
WriteNewLine();
Write("def __init__(self)");
OpenChild();
if (klass.Constructor != null)
WriteDoc(klass.Constructor.Documentation);
if (InheritsConstructor(klass)) {
WriteName(klass.Parent);
WriteLine(".__init__(self)");
}
WriteConstructorBody(klass);
CloseChild();
}
WriteMembers(klass, true);
CloseChild();
this.WrittenTypes.Add(klass);
}
void WriteResourceByte!(int b)
{
Write($"\\x{b:x2}");
}
void WriteResources!(SortedDictionary<string(), List<byte>()> resources)
{
if (resources.Count == 0)
return;
WriteNewLine();
Write("class _FuResource");
OpenChild();
foreach ((string name, List<byte> content) in resources) {
WriteResourceName(name);
WriteLine(" = (");
this.Indent++;
Write("b\"");
int i = 0;
foreach (byte b in content) {
if (i > 0 && (i & 15) == 0) {
WriteCharLine('"');
Write("b\"");
}
WriteResourceByte(b);
i++;
}
WriteLine("\" )");
this.Indent--;
}
CloseChild();
}
void WriteMain!(FuMethod main)
{
WriteNewLine();
WriteLine("if __name__ == \"__main__\":");
WriteChar('\t');
if (main.Type.Id == FuId.IntType)
Write("sys.exit(");
WriteName(main.Parent);
Write(".main(");
if (main.Parameters.Count() == 1)
Write("sys.argv[1:]");
if (main.Type.Id == FuId.IntType)
WriteChar(')');
WriteCharLine(')');
}
public override void WriteProgram!(FuProgram program, string outputFile, string namespace)
{
this.WrittenTypes.Clear();
this.WrittenTypes.Add(program.System.ExceptionClass);
this.TimeNs = false;
this.SwitchBreak = false;
OpenStringWriter();
WriteTypes(program);
CreateFile(null, outputFile);
WriteTopLevelNatives(program);
if (program.Main != null && (program.Main.Type.Id == FuId.IntType || program.Main.Parameters.Count() == 1))
Include("sys");
WriteIncludes("import ", "");
if (this.TimeNs)
WriteLine("from time import time_ns");
if (this.SwitchBreak) {
WriteNewLine();
WriteLine("class _CiBreak(Exception): pass");
}
CloseStringWriter();
WriteResources(program.Resources);
if (program.Main != null)
WriteMain(program.Main);
CloseFile();
}
}