87627cb8创建于 7月13日历史提交
// 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();
	}
}