// GenBase.fu - base class for code generators
//
// Copyright (C) 2011-2026 Piotr Fusik
//
// This file is part of Fusion Transpiler,
// see https://github.com/fusionlanguage/fut
//
// Fusion Transpiler is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Fusion Transpiler is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Fusion Transpiler. If not, see http://www.gnu.org/licenses/
public abstract class GenHost : FuSemaHost
{
public abstract TextWriter! CreateFile!(string? directory, string filename);
public abstract void CloseFile!();
}
public abstract class GenBase : FuVisitor
{
GenHost! Host;
TextWriter! Writer;
StringWriter() StringWriter;
protected int Indent = 0;
protected bool AtLineStart = true;
bool AtChildStart = false;
bool InChildBlock = false;
protected bool InHeaderFile = false;
SortedDictionary<string(), bool>() Includes;
protected FuMethodBase? CurrentMethod = null;
protected HashSet<FuContainerType>() WrittenTypes;
protected List<FuSwitch>() SwitchesWithGoto;
protected List<FuExpr>() CurrentTemporaries; // FuExpr or FuType
public void SetHost!(GenHost! host)
{
this.Host = host;
}
protected abstract string GetTargetName();
void ReportError(FuStatement statement, string message)
{
this.Host.ReportStatementError(statement, message);
}
protected void NotSupported(FuStatement statement, string feature)
{
ReportError(statement, $"{feature} not supported when targeting {GetTargetName()}");
}
protected void NotYet(FuStatement statement, string feature)
{
ReportError(statement, $"{feature} not supported yet when targeting {GetTargetName()}");
}
protected virtual void StartLine!()
{
if (this.AtLineStart) {
if (this.AtChildStart) {
this.AtChildStart = false;
this.Writer.WriteChar('\n');
this.Indent++;
}
for (int i = 0; i < this.Indent; i++)
this.Writer.WriteChar('\t');
this.AtLineStart = false;
}
}
protected void WriteChar!(int c)
{
StartLine();
this.Writer.WriteCodePoint(c);
}
protected void Write!(string s)
{
StartLine();
this.Writer.Write(s);
}
internal override void VisitLiteralNull!()
{
Write("null");
}
internal override void VisitLiteralFalse!()
{
Write("false");
}
internal override void VisitLiteralTrue!()
{
Write("true");
}
internal override void VisitLiteralLong!(long i, FuPriority parent)
{
this.Writer.Write(i);
}
protected virtual int GetLiteralChars() => 0;
internal override void VisitLiteralChar!(int c)
{
if (c < GetLiteralChars()) {
WriteChar('\'');
switch (c) {
case '\n': Write("\\n"); break;
case '\r': Write("\\r"); break;
case '\t': Write("\\t"); break;
case '\'': Write("\\'"); break;
case '\\': Write("\\\\"); break;
default: WriteChar(c); break;
}
WriteChar('\'');
}
else
this.Writer.Write(c);
}
internal override void VisitLiteralDouble!(double value)
{
string() s = $"{value}";
Write(s);
foreach (int c in s) {
switch (c) {
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
default:
return;
}
}
Write(".0"); // it looked like an integer
}
internal override void VisitLiteralString!(string value)
{
WriteChar('"');
Write(value);
WriteChar('"');
}
void WriteLowercaseChar!(int c)
{
if (c >= 'A' && c <= 'Z')
c += 0x20;
this.Writer.WriteChar(c);
}
void WriteUppercaseChar!(int c)
{
if (c >= 'a' && c <= 'z')
c -= 0x20;
this.Writer.WriteChar(c);
}
protected void WriteLowercase!(string s)
{
StartLine();
foreach (int c in s)
WriteLowercaseChar(c);
}
protected void WriteCamelCase!(string s)
{
StartLine();
WriteLowercaseChar(s[0]);
this.Writer.Write(s.Substring(1));
}
protected void WritePascalCase!(string s)
{
StartLine();
WriteUppercaseChar(s[0]);
this.Writer.Write(s.Substring(1));
}
protected void WriteUppercaseWithUnderscores!(string s)
{
StartLine();
bool first = true;
foreach (int c in s) {
if (!first && c >= 'A' && c <= 'Z') {
this.Writer.WriteChar('_');
this.Writer.WriteChar(c);
}
else
WriteUppercaseChar(c);
first = false;
}
}
protected void WriteLowercaseWithUnderscores!(string s)
{
StartLine();
bool first = true;
foreach (int c in s) {
if (c >= 'A' && c <= 'Z') {
if (!first)
this.Writer.WriteChar('_');
WriteLowercaseChar(c);
}
else
this.Writer.WriteChar(c);
first = false;
}
}
protected void WriteNewLine!()
{
this.Writer.WriteChar('\n');
this.AtLineStart = true;
}
protected void WriteCharLine!(int c)
{
WriteChar(c);
WriteNewLine();
}
protected void WriteLine!(string s)
{
Write(s);
WriteNewLine();
}
protected void WriteUppercaseConstName!(FuConst konst)
{
if (konst.InMethod != null) {
WriteUppercaseWithUnderscores(konst.InMethod.Name);
WriteChar('_');
}
WriteUppercaseWithUnderscores(konst.Name);
if (konst.InMethodIndex > 0) {
WriteChar('_');
VisitLiteralLong(konst.InMethodIndex, FuPriority.Primary);
}
}
protected abstract void WriteName!(FuSymbol symbol);
protected virtual void WriteBanner!()
{
WriteLine("// Generated automatically with \"fut\". Do not edit.");
}
protected void CreateFile!(string? directory, string filename)
{
this.Writer = this.Host.CreateFile(directory, filename);
WriteBanner();
}
protected void CloseFile!()
{
this.Host.CloseFile();
}
protected void OpenStringWriter!()
{
this.Writer = this.StringWriter;
}
protected void CloseStringWriter!()
{
this.Writer.Write(this.StringWriter.ToString());
this.StringWriter.Clear();
}
protected void Include!(string name)
{
if (!this.Includes.ContainsKey(name))
this.Includes[name] = this.InHeaderFile;
}
protected void WriteIncludes!(string prefix, string suffix)
{
foreach ((string name, bool inHeaderFile) in this.Includes) {
if (inHeaderFile == this.InHeaderFile) {
Write(prefix);
Write(name);
WriteLine(suffix);
}
}
if (!this.InHeaderFile)
this.Includes.Clear();
}
protected virtual void StartDocLine!()
{
Write(" * ");
}
protected void WriteXmlDoc!(string text)
{
foreach (int c in text) {
switch (c) {
case '&':
Write("&");
break;
case '<':
Write("<");
break;
case '>':
Write(">");
break;
default:
WriteChar(c);
break;
}
}
}
protected virtual void WriteDocCode!(string s)
{
WriteXmlDoc(s);
}
protected virtual void WriteDocPara!(FuDocPara para, bool many)
{
if (many) {
WriteNewLine();
Write(" * <p>");
}
foreach (FuDocInline inline in para.Children) {
switch (inline) {
case FuDocText text:
WriteXmlDoc(text.Text);
break;
case FuDocCode code:
Write("<code>");
WriteDocCode(code.Text);
Write("</code>");
break;
case FuDocLine:
WriteNewLine();
StartDocLine();
break;
default:
assert false;
}
}
}
protected virtual void WriteDocList!(FuDocList list)
{
WriteNewLine();
WriteLine(" * <ul>");
foreach (FuDocPara item in list.Items) {
Write(" * <li>");
WriteDocPara(item, false);
WriteLine("</li>");
}
Write(" * </ul>");
}
protected void WriteDocBlock!(FuDocBlock block, bool many)
{
switch (block) {
case FuDocPara para:
WriteDocPara(para, many);
break;
case FuDocList list:
WriteDocList(list);
break;
default:
assert false;
}
}
protected void WriteContent!(FuCodeDoc doc)
{
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 virtual void WriteDoc!(FuCodeDoc? doc)
{
if (doc != null) {
WriteLine("/**");
WriteContent(doc);
WriteLine(" */");
}
}
protected virtual void WriteSelfDoc!(FuMethod method)
{
}
protected virtual void WriteParameterDoc!(FuVar param, bool first)
{
Write(" * @param ");
WriteName(param);
WriteChar(' ');
WriteDocPara(param.Documentation.Summary, false);
WriteNewLine();
}
protected virtual void WriteReturnDoc!(FuMethod method)
{
}
protected virtual void WriteThrowsDoc!(FuThrowsDeclaration decl)
{
Write(" * @throws ");
WriteExceptionClass(decl.Symbol);
WriteChar(' ');
WriteDocPara(decl.Documentation.Summary, false);
WriteNewLine();
}
protected void WriteParametersAndThrowsDoc!(FuMethod method)
{
bool first = true;
for (FuVar? param = method.FirstParameter(); param != null; param = param.NextVar()) {
if (param.Documentation != null) {
WriteParameterDoc(param, first);
first = false;
}
}
WriteReturnDoc(method);
foreach (FuThrowsDeclaration# decl in method.Throws) {
if (decl.Documentation != null)
WriteThrowsDoc(decl);
}
}
protected void WriteMethodDoc!(FuMethod method)
{
if (method.Documentation == null)
return;
WriteLine("/**");
WriteContent(method.Documentation);
WriteSelfDoc(method);
WriteParametersAndThrowsDoc(method);
WriteLine(" */");
}
protected void WriteTopLevelNatives!(FuProgram program)
{
foreach (string content in program.TopLevelNatives)
Write(content);
}
protected void OpenBlock!()
{
WriteCharLine('{');
this.Indent++;
}
protected void CloseBlock!()
{
this.Indent--;
WriteCharLine('}');
}
protected virtual void EndStatement!()
{
WriteCharLine(';');
}
protected void WriteComma!(int i)
{
if (i > 0) {
if ((i & 15) == 0) {
WriteCharLine(',');
WriteChar('\t');
}
else
Write(", ");
}
}
protected void WriteBytes!(List<byte> content)
{
int i = 0;
foreach (byte b in content) {
WriteComma(i++);
VisitLiteralLong(b, FuPriority.Argument);
}
}
protected virtual FuId GetTypeId(FuType type, bool promote) => promote && type is FuRangeType ? FuId.IntType : type.Id;
protected abstract void WriteTypeAndName!(FuNamedValue value);
protected virtual void WriteLocalName!(FuSymbol symbol, FuPriority parent)
{
if (symbol is FuField)
Write("this.");
WriteName(symbol);
}
protected void WriteDoubling!(string s, int doubled)
{
foreach (int c in s) {
if (c == doubled)
WriteChar(c);
WriteChar(c);
}
}
protected void WriteDoublingBraces!(string s)
{
foreach (int c in s) {
if (c == '{' || c == '}')
WriteChar(c);
WriteChar(c);
}
}
protected virtual void WritePrintfWidth!(FuInterpolatedPart part)
{
if (part.WidthExpr != null)
VisitLiteralLong(part.Width, FuPriority.Primary);
if (part.Precision >= 0) {
WriteChar('.');
VisitLiteralLong(part.Precision, FuPriority.Primary);
}
}
static int GetPrintfFormat(FuType type, int format)
{
switch (type) {
case FuIntegerType:
switch (format) {
case 'X':
case 'x':
return format;
case 'U':
case 'u':
return 'c';
default:
return 'd';
}
case FuNumericType:
switch (format) {
case 'E':
case 'e':
case 'f':
case 'G':
return format;
case 'F':
return 'f';
default:
return 'g';
}
// case FuStringType: - matched by FuClassType
case FuClassType:
return 's';
default:
assert false;
}
}
protected virtual void WritePrintfPartFormat!(FuInterpolatedPart part)
{
WritePrintfWidth(part);
WriteChar(GetPrintfFormat(part.Argument.Type, part.Format));
}
protected void WritePrintfFormat!(FuInterpolatedString expr)
{
foreach (FuInterpolatedPart part in expr.Parts) {
WriteDoubling(part.Prefix, '%');
WriteChar('%');
WritePrintfPartFormat(part);
}
WriteDoubling(expr.Suffix, '%');
}
protected void WritePyFormat!(FuInterpolatedPart part)
{
if (part.WidthExpr != null || part.Precision >= 0 || (part.Format != ' ' && part.Format != 'D'))
WriteChar(':');
if (part.WidthExpr != null) {
if (part.Width >= 0) {
if (!(part.Argument.Type is FuNumericType))
WriteChar('>');
VisitLiteralLong(part.Width, FuPriority.Primary);
}
else {
WriteChar('<');
VisitLiteralLong(-part.Width, FuPriority.Primary);
}
}
if (part.Precision >= 0) {
WriteChar(part.Argument.Type is FuIntegerType ? '0' : '.');
VisitLiteralLong(part.Precision, FuPriority.Primary);
}
switch (part.Format) {
case ' ':
case 'D':
break;
case 'U':
case 'u':
WriteChar('c');
break;
default:
WriteChar(part.Format);
break;
}
WriteChar('}');
}
protected virtual void WriteInterpolatedStringArg!(FuInterpolatedPart part)
{
part.Argument.Accept(this, FuPriority.Argument);
}
protected void WriteInterpolatedStringArgs!(FuInterpolatedString expr)
{
foreach (FuInterpolatedPart part in expr.Parts) {
Write(", ");
WriteInterpolatedStringArg(part);
}
}
protected void WritePrintf!(FuInterpolatedString expr, bool newLine)
{
WriteChar('"');
WritePrintfFormat(expr);
if (newLine)
Write("\\n");
WriteChar('"');
WriteInterpolatedStringArgs(expr);
WriteChar(')');
}
protected void WritePostfix!(FuExpr obj, string s)
{
obj.Accept(this, FuPriority.Primary);
Write(s);
}
protected void WriteCall!(string function, FuExpr arg0, FuExpr? arg1 = null, FuExpr? arg2 = null)
{
Write(function);
WriteChar('(');
arg0.Accept(this, FuPriority.Argument);
if (arg1 != null) {
Write(", ");
arg1.Accept(this, FuPriority.Argument);
if (arg2 != null) {
Write(", ");
arg2.Accept(this, FuPriority.Argument);
}
}
WriteChar(')');
}
protected virtual void WriteMemberOp!(FuExpr left, FuSymbolReference? symbol)
{
WriteChar('.');
}
protected void WriteMethodCall!(FuExpr obj, string method, FuExpr arg0, FuExpr? arg1 = null)
{
obj.Accept(this, FuPriority.Primary);
WriteMemberOp(obj, null);
WriteCall(method, arg0, arg1);
}
protected void WriteInParentheses!(List<FuExpr#> args)
{
WriteChar('(');
bool first = true;
foreach (FuExpr arg in args) {
if (!first)
Write(", ");
arg.Accept(this, FuPriority.Argument);
first = false;
}
WriteChar(')');
}
protected virtual void WriteSelectValues!(FuType type, FuSelectExpr expr)
{
WriteCoerced(type, expr.OnTrue, FuPriority.Select);
Write(" : ");
WriteCoerced(type, expr.OnFalse, FuPriority.Select);
}
protected virtual void WriteCoercedSelect!(FuType type, FuSelectExpr expr, FuPriority parent)
{
if (parent > FuPriority.Select)
WriteChar('(');
expr.Cond.Accept(this, FuPriority.SelectCond);
Write(" ? ");
WriteSelectValues(type, expr);
if (parent > FuPriority.Select)
WriteChar(')');
}
protected virtual void WriteCoercedInternal!(FuType type, FuExpr expr, FuPriority parent)
{
expr.Accept(this, parent);
}
protected void WriteCoerced!(FuType type, FuExpr expr, FuPriority parent)
{
if (expr is FuSelectExpr select)
WriteCoercedSelect(type, select, parent);
else
WriteCoercedInternal(type, expr, parent);
}
protected virtual void WriteCoercedExpr!(FuType type, FuExpr expr)
{
WriteCoerced(type, expr, FuPriority.Argument);
}
protected virtual void WriteStronglyCoerced!(FuType type, FuExpr expr)
{
WriteCoerced(type, expr, FuPriority.Argument);
}
protected virtual void WriteCoercedLiteral!(FuType? type, FuExpr expr)
{
expr.Accept(this, FuPriority.Argument);
}
protected void WriteCoercedLiterals!(FuType? type, List<FuExpr#> exprs)
{
for (int i = 0; i < exprs.Count; i++) {
WriteComma(i);
WriteCoercedLiteral(type, exprs[i]);
}
}
protected void WriteCoercedArgs!(FuMethod method, List<FuExpr#> args)
{
FuVar param = method.FirstParameter();
bool first = true;
foreach (FuExpr arg in args) {
if (!first)
Write(", ");
first = false;
WriteStronglyCoerced(param.Type, arg);
param = param.NextVar();
}
}
protected void WriteCoercedArgsInParentheses!(FuMethod method, List<FuExpr#> args)
{
WriteChar('(');
WriteCoercedArgs(method, args);
WriteChar(')');
}
protected abstract void WriteNewArray!(FuType elementType, FuExpr lengthExpr, FuPriority parent);
protected virtual void WriteNewArrayStorage!(FuArrayStorageType array)
{
WriteNewArray(array.GetElementType(), array.LengthExpr, FuPriority.Argument);
}
protected abstract void WriteNew!(FuReadWriteClassType klass, FuPriority parent);
protected void WriteNewStorage!(FuType type)
{
switch (type) {
case FuArrayStorageType array:
WriteNewArrayStorage(array);
break;
case FuStorageType storage:
WriteNew(storage, FuPriority.Argument);
break;
default:
assert false;
}
}
protected virtual void WriteArrayStorageInit!(FuArrayStorageType array, FuExpr? value)
{
Write(" = ");
WriteNewArrayStorage(array);
}
protected virtual void WriteNewWithFields!(FuReadWriteClassType type, FuAggregateInitializer init)
{
WriteNew(type, FuPriority.Argument);
}
protected virtual void WriteStorageInit!(FuNamedValue def)
{
Write(" = ");
if (def.Value is FuAggregateInitializer init) {
assert def.Type is FuReadWriteClassType klass;
WriteNewWithFields(klass, init);
}
else
WriteNewStorage(def.Type);
}
protected virtual void WriteVarInit!(FuNamedValue def)
{
if (def.Type is FuArrayStorageType array)
WriteArrayStorageInit(array, def.Value);
else if (def.Type.IsFinal() && def.Value is FuLiteralNull) {
}
else if (def.Value != null && !(def.Value is FuAggregateInitializer)) {
Write(" = ");
WriteCoercedExpr(def.Type, def.Value);
}
else if (def.Type.IsFinal() && !(def.Parent is FuParameters))
WriteStorageInit(def);
}
protected virtual void WriteVar!(FuNamedValue def)
{
WriteTypeAndName(def);
WriteVarInit(def);
}
internal override void VisitVar!(FuVar expr)
{
WriteVar(expr);
}
protected void WriteObjectLiteral!(FuAggregateInitializer init, string separator)
{
string prefix = " { ";
foreach (FuExpr item in init.Items) {
Write(prefix);
assert item is FuBinaryExpr assign;
assert assign.Left is FuSymbolReference field;
WriteName(field.Symbol);
Write(separator);
WriteCoerced(assign.Left.Type, assign.Right, FuPriority.Argument);
prefix = ", ";
}
Write(" }");
}
static FuAggregateInitializer? GetAggregateInitializer(FuNamedValue def)
{
FuExpr expr = def.Value;
if (expr is FuPrefixExpr unary)
expr = unary.Inner;
return expr is FuAggregateInitializer init ? init : null;
}
void WriteAggregateInitField!(FuExpr obj, FuExpr item)
{
assert item is FuBinaryExpr assign;
assert assign.Left is FuSymbolReference field;
WriteMemberOp(obj, field);
WriteName(field.Symbol);
Write(" = ");
WriteCoerced(field.Type, assign.Right, FuPriority.Argument);
EndStatement();
}
protected virtual void WriteInitCode!(FuNamedValue def)
{
FuAggregateInitializer? init = GetAggregateInitializer(def);
if (init != null) {
foreach (FuExpr item in init.Items) {
WriteLocalName(def, FuPriority.Primary);
WriteAggregateInitField(def, item);
}
}
}
protected virtual void DefineIsVar!(FuBinaryExpr binary)
{
if (binary.Right is FuVar def) {
EnsureChildBlock();
WriteVar(def);
EndStatement();
}
}
protected void WriteArrayElement!(FuNamedValue def, int nesting)
{
WriteLocalName(def, FuPriority.Primary);
for (int i = 0; i < nesting; i++) {
Write("[_i");
VisitLiteralLong(i, FuPriority.Primary);
WriteChar(']');
}
}
protected void OpenLoop!(string intString, int nesting, int count)
{
Write("for (");
Write(intString);
Write(" _i");
VisitLiteralLong(nesting, FuPriority.Primary);
Write(" = 0; _i");
VisitLiteralLong(nesting, FuPriority.Primary);
Write(" < ");
VisitLiteralLong(count, FuPriority.Rel);
Write("; _i");
VisitLiteralLong(nesting, FuPriority.Primary);
Write("++) ");
OpenBlock();
}
protected void WriteTemporaryName!(int id)
{
Write("futemp");
VisitLiteralLong(id, FuPriority.Primary);
}
protected bool TryWriteTemporary!(FuExpr expr)
{
int id = this.CurrentTemporaries.IndexOf(expr);
if (id < 0)
return false;
WriteTemporaryName(id);
return true;
}
protected void WriteResourceName!(string name)
{
foreach (int c in name)
WriteChar(FuLexer.IsLetterOrDigit(c) ? c : '_');
}
protected abstract void WriteResource!(string name, int length);
internal override void VisitPrefixExpr!(FuPrefixExpr expr, FuPriority parent)
{
switch (expr.Op) {
case FuToken.Increment:
Write("++");
break;
case FuToken.Decrement:
Write("--");
break;
case FuToken.Minus:
WriteChar('-');
// FIXME: - --foo[bar]
if (expr.Inner is FuPrefixExpr inner && (inner.Op == FuToken.Minus || inner.Op == FuToken.Decrement))
WriteChar(' ');
break;
case FuToken.Tilde:
WriteChar('~');
break;
case FuToken.ExclamationMark:
WriteChar('!');
break;
case FuToken.New:
assert expr.Type is FuDynamicPtrType dynamic;
if (TryWriteTemporary(expr))
return;
if (dynamic.Class.Id == FuId.ArrayPtrClass)
WriteNewArray(dynamic.GetElementType(), expr.Inner, parent);
else if (expr.Inner is FuAggregateInitializer init)
WriteNewWithFields(dynamic, init);
else
WriteNew(dynamic, parent);
return;
case FuToken.Resource:
assert expr.Inner is FuLiteralString name;
assert expr.Type is FuArrayStorageType array;
WriteResource(name.Value, array.Length);
return;
default:
assert false;
}
expr.Inner.Accept(this, FuPriority.Primary);
}
internal override void VisitPostfixExpr!(FuPostfixExpr expr, FuPriority parent)
{
expr.Inner.Accept(this, FuPriority.Primary);
switch (expr.Op) {
case FuToken.Increment:
Write("++");
break;
case FuToken.Decrement:
Write("--");
break;
default:
assert false;
}
}
protected bool IsWholeArray(FuExpr array, FuExpr offset, FuExpr length) => array.Type is FuArrayStorageType arrayStorage
&& offset.IsLiteralZero() && length is FuLiteralLong literalLength && arrayStorage.Length == literalLength.Value;
protected void StartAdd!(FuExpr expr)
{
if (!expr.IsLiteralZero()) {
expr.Accept(this, FuPriority.Add);
Write(" + ");
}
}
protected void WriteAdd!(FuExpr left, FuExpr right)
{
if (left is FuLiteralLong leftLiteral) {
long leftValue = leftLiteral.Value;
if (leftValue == 0) {
right.Accept(this, FuPriority.Argument);
return;
}
if (right is FuLiteralLong rightLiteral) {
VisitLiteralLong(leftValue + rightLiteral.Value, FuPriority.Argument);
return;
}
}
else if (right.IsLiteralZero()) {
left.Accept(this, FuPriority.Argument);
return;
}
left.Accept(this, FuPriority.Add);
Write(" + ");
right.Accept(this, FuPriority.Add);
}
protected void WriteStartEnd!(FuExpr startIndex, FuExpr length)
{
startIndex.Accept(this, FuPriority.Argument);
Write(", ");
WriteAdd(startIndex, length); // FIXME: side effect
}
protected static bool NeedAddParentheses(FuPriority parent)
{
switch (parent) {
case FuPriority.Or:
case FuPriority.Xor:
case FuPriority.And:
case FuPriority.Shift:
return true;
default:
return parent > FuPriority.Add;
}
}
protected virtual void WriteBinaryOperand!(FuExpr expr, FuPriority parent, FuBinaryExpr binary)
{
expr.Accept(this, parent);
}
protected void WriteBinaryExpr!(FuBinaryExpr expr, bool parentheses, FuPriority left, string op, FuPriority right)
{
if (parentheses)
WriteChar('(');
WriteBinaryOperand(expr.Left, left, expr);
Write(op);
WriteBinaryOperand(expr.Right, right, expr);
if (parentheses)
WriteChar(')');
}
protected void WriteBinaryExpr2!(FuBinaryExpr expr, FuPriority parent, FuPriority child, string op)
{
WriteBinaryExpr(expr, parent > child, child, op, child);
}
protected static string GetEqOp(bool not) => not ? " != " : " == ";
protected virtual void WriteEqualOperand!(FuExpr expr, FuExpr other)
{
expr.Accept(this, FuPriority.Equality);
}
protected void WriteEqualExpr!(FuExpr? left, FuExpr right, FuPriority parent, string op)
{
if (parent > FuPriority.CondAnd)
WriteChar('(');
if (left == null)
Write("fuSwitchValue");
else
WriteEqualOperand(left, right);
Write(op);
WriteEqualOperand(right, left);
if (parent > FuPriority.CondAnd)
WriteChar(')');
}
protected virtual void WriteEqual!(FuExpr? left, FuExpr right, FuPriority parent, bool not)
{
WriteEqualExpr(left, right, parent, GetEqOp(not));
}
protected virtual void WriteRel!(FuBinaryExpr expr, FuPriority parent, string op)
{
WriteBinaryExpr(expr, parent > FuPriority.CondAnd, FuPriority.Rel, op, FuPriority.Rel);
}
protected virtual void WriteAnd!(FuBinaryExpr expr, FuPriority parent)
{
WriteBinaryExpr(expr, parent > FuPriority.CondAnd && parent != FuPriority.And, FuPriority.And, " & ", FuPriority.And);
}
protected virtual void WriteAssignRight!(FuBinaryExpr expr)
{
WriteCoerced(expr.Left.Type, expr.Right, FuPriority.Argument);
}
protected virtual void WriteAssign!(FuBinaryExpr expr, FuPriority parent)
{
if (parent > FuPriority.Assign)
WriteChar('(');
expr.Left.Accept(this, FuPriority.Assign);
Write(" = ");
WriteAssignRight(expr);
if (parent > FuPriority.Assign)
WriteChar(')');
}
protected virtual void WriteOpAssignRight!(FuBinaryExpr expr)
{
expr.Right.Accept(this, FuPriority.Argument);
}
protected virtual void WriteIndexingInfix!(FuExpr collection)
{
}
protected void WriteIndexing!(FuExpr collection, FuExpr index)
{
collection.Accept(this, FuPriority.Primary);
WriteIndexingInfix(collection);
WriteChar('[');
index.Accept(this, FuPriority.Argument);
WriteChar(']');
}
protected virtual void WriteIndexingExpr!(FuBinaryExpr expr, FuPriority parent)
{
WriteIndexing(expr.Left, expr.Right);
}
protected virtual string GetIsOperator() => " is ";
internal override void VisitBinaryExpr!(FuBinaryExpr expr, FuPriority parent)
{
switch (expr.Op) {
case FuToken.Plus:
WriteBinaryExpr(expr, NeedAddParentheses(parent), FuPriority.Add, " + ", FuPriority.Add);
break;
case FuToken.Minus:
WriteBinaryExpr(expr, NeedAddParentheses(parent), FuPriority.Add, " - ", FuPriority.Mul);
break;
case FuToken.Asterisk:
WriteBinaryExpr(expr, parent > FuPriority.Mul, FuPriority.Mul, " * ", FuPriority.Primary);
break;
case FuToken.Slash:
WriteBinaryExpr(expr, parent > FuPriority.Mul, FuPriority.Mul, " / ", FuPriority.Primary);
break;
case FuToken.Mod:
WriteBinaryExpr(expr, parent > FuPriority.Mul, FuPriority.Mul, " % ", FuPriority.Primary);
break;
case FuToken.ShiftLeft:
WriteBinaryExpr(expr, parent > FuPriority.Shift, FuPriority.Shift, " << ", FuPriority.Mul);
break;
case FuToken.ShiftRight:
WriteBinaryExpr(expr, parent > FuPriority.Shift, FuPriority.Shift, " >> ", FuPriority.Mul);
break;
case FuToken.Equal:
WriteEqual(expr.Left, expr.Right, parent, false);
break;
case FuToken.NotEqual:
WriteEqual(expr.Left, expr.Right, parent, true);
break;
case FuToken.Less:
WriteRel(expr, parent, " < ");
break;
case FuToken.LessOrEqual:
WriteRel(expr, parent, " <= ");
break;
case FuToken.Greater:
WriteRel(expr, parent, " > ");
break;
case FuToken.GreaterOrEqual:
WriteRel(expr, parent, " >= ");
break;
case FuToken.And:
WriteAnd(expr, parent);
break;
case FuToken.Or:
WriteBinaryExpr2(expr, parent, FuPriority.Or, " | ");
break;
case FuToken.Xor:
WriteBinaryExpr(expr, parent > FuPriority.Xor || parent == FuPriority.Or, FuPriority.Xor, " ^ ", FuPriority.Xor);
break;
case FuToken.CondAnd:
WriteBinaryExpr(expr, parent > FuPriority.CondAnd || parent == FuPriority.CondOr, FuPriority.CondAnd, " && ", FuPriority.CondAnd);
break;
case FuToken.CondOr:
WriteBinaryExpr2(expr, parent, FuPriority.CondOr, " || ");
break;
case FuToken.Assign:
WriteAssign(expr, parent);
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:
if (parent > FuPriority.Assign)
WriteChar('(');
expr.Left.Accept(this, FuPriority.Assign);
WriteChar(' ');
Write(expr.GetOpString());
WriteChar(' ');
WriteOpAssignRight(expr);
if (parent > FuPriority.Assign)
WriteChar(')');
break;
case FuToken.LeftBracket:
if (expr.Left.Type is FuStringType)
WriteCharAt(expr);
else
WriteIndexingExpr(expr, parent);
break;
case FuToken.Is:
if (parent > FuPriority.Rel)
WriteChar('(');
expr.Left.Accept(this, FuPriority.Rel);
Write(GetIsOperator());
switch (expr.Right) {
case FuSymbolReference symbol:
WriteName(symbol.Symbol);
break;
case FuVar def:
WriteTypeAndName(def);
break;
default:
assert false;
}
if (parent > FuPriority.Rel)
WriteChar(')');
break;
default:
assert false;
}
}
protected abstract void WriteStringLength!(FuExpr expr);
protected virtual void WriteArrayLength!(FuExpr expr, FuPriority parent)
{
WritePostfix(expr, ".length");
}
protected static bool IsReferenceTo(FuExpr expr, FuId id) => expr is FuSymbolReference symbol && symbol.Symbol.Id == id;
protected static bool IsConsoleStream(FuExpr expr) => expr is FuSymbolReference symbol && (symbol.Symbol.Id == FuId.ConsoleError || symbol.Symbol.Id == FuId.ConsoleOut);
protected bool WriteJavaMatchProperty!(FuSymbolReference expr, FuPriority parent)
{
switch (expr.Symbol.Id) {
case FuId.MatchStart:
WritePostfix(expr.Left, ".start()");
return true;
case FuId.MatchEnd:
WritePostfix(expr.Left, ".end()");
return true;
case FuId.MatchLength:
if (parent > FuPriority.Add)
WriteChar('(');
WritePostfix(expr.Left, ".end() - ");
WritePostfix(expr.Left, ".start()"); // FIXME: side effect
if (parent > FuPriority.Add)
WriteChar(')');
return true;
case FuId.MatchValue:
WritePostfix(expr.Left, ".group()");
return true;
default:
return false;
}
}
internal override void VisitSymbolReference!(FuSymbolReference expr, FuPriority parent)
{
if (expr.Left == null)
WriteLocalName(expr.Symbol, parent);
else if (expr.Symbol.Id == FuId.StringLength)
WriteStringLength(expr.Left);
else if (expr.Symbol.Id == FuId.ArrayLength)
WriteArrayLength(expr.Left, parent);
else {
expr.Left.Accept(this, FuPriority.Primary);
WriteMemberOp(expr.Left, expr);
WriteName(expr.Symbol);
}
}
protected abstract void WriteCharAt!(FuBinaryExpr expr);
protected virtual void WriteNotPromoted!(FuType type, FuExpr expr)
{
expr.Accept(this, FuPriority.Argument);
}
protected virtual void WriteEnumAsInt!(FuExpr expr, FuPriority parent)
{
expr.Accept(this, parent);
}
protected void WriteEnumHasFlag!(FuExpr obj, List<FuExpr#> args, FuPriority parent)
{
if (parent > FuPriority.Equality)
WriteChar('(');
int i = args[0].IntValue();
if ((i & i - 1) == 0 && i != 0) { // TODO: BitOperations.IsPow2
WriteChar('(');
WriteEnumAsInt(obj, FuPriority.And);
Write(" & ");
WriteEnumAsInt(args[0], FuPriority.And);
Write(") != 0");
}
else {
Write("(~");
WriteEnumAsInt(obj, FuPriority.Primary);
Write(" & ");
WriteEnumAsInt(args[0], FuPriority.And);
Write(") == 0");
}
if (parent > FuPriority.Equality)
WriteChar(')');
}
protected void WriteTryParseRadix!(List<FuExpr#> args)
{
Write(", ");
if (args.Count == 2)
args[1].Accept(this, FuPriority.Argument);
else
Write("10");
}
protected void WriteListAdd!(FuExpr obj, string method, List<FuExpr#> args)
{
obj.Accept(this, FuPriority.Primary);
WriteChar('.');
Write(method);
WriteChar('(');
FuType elementType = obj.Type.AsClassType().GetElementType();
if (args.Count == 0)
WriteNewStorage(elementType);
else
WriteNotPromoted(elementType, args[0]);
WriteChar(')');
}
protected void WriteListInsert!(FuExpr obj, string method, List<FuExpr#> args, string separator = ", ")
{
obj.Accept(this, FuPriority.Primary);
WriteChar('.');
Write(method);
WriteChar('(');
args[0].Accept(this, FuPriority.Argument);
Write(separator);
FuType elementType = obj.Type.AsClassType().GetElementType();
if (args.Count == 1)
WriteNewStorage(elementType);
else
WriteNotPromoted(elementType, args[1]);
WriteChar(')');
}
protected void WriteDictionaryAdd!(FuExpr obj, List<FuExpr#> args)
{
WriteIndexing(obj, args[0]);
Write(" = ");
WriteNewStorage(obj.Type.AsClassType().GetValueType());
}
protected void WriteContains!(FuExpr haystack, FuExpr needle, FuPriority parent)
{
if (parent > FuPriority.And)
WriteChar('(');
needle.Accept(this, FuPriority.Rel);
Write(" in ");
haystack.Accept(this, FuPriority.Primary);
if (parent > FuPriority.And)
WriteChar(')');
}
protected void WriteClampAsMinMax!(List<FuExpr#> args)
{
args[0].Accept(this, FuPriority.Argument);
Write(", ");
args[1].Accept(this, FuPriority.Argument);
Write("), ");
args[2].Accept(this, FuPriority.Argument);
WriteChar(')');
}
protected void WriteRegexLiteral!(string s)
{
WriteChar('/');
bool escaped = false;
foreach (int c in s) {
switch (c) {
case '\\':
if (!escaped) {
escaped = true;
continue;
}
escaped = false;
break;
case '"':
case '\'':
escaped = false;
break;
case '/':
escaped = true;
break;
default:
break;
}
if (escaped) {
WriteChar('\\');
escaped = false;
}
WriteChar(c);
}
WriteChar('/');
}
protected RegexOptions GetRegexOptions(List<FuExpr#> args)
{
FuExpr expr = args.Last();
if (expr.Type is FuEnum)
return RegexOptions.FromInt(expr.IntValue());
return RegexOptions.None;
}
protected bool WriteRegexOptions!(List<FuExpr#> args, string prefix, string separator, string suffix, string i, string m, string s)
{
RegexOptions options = GetRegexOptions(args);
if (options == RegexOptions.None)
return false;
Write(prefix);
if (options.HasFlag(RegexOptions.IgnoreCase))
Write(i);
if (options.HasFlag(RegexOptions.Multiline)) {
if (options.HasFlag(RegexOptions.IgnoreCase))
Write(separator);
Write(m);
}
if (options.HasFlag(RegexOptions.Singleline)) {
if (options != RegexOptions.Singleline)
Write(separator);
Write(s);
}
Write(suffix);
return true;
}
protected abstract void WriteCallExpr!(FuType type, FuExpr? obj, FuMethod method, List<FuExpr#> args, FuPriority parent);
internal override void VisitCallExpr!(FuCallExpr expr, FuPriority parent)
{
assert expr.Method.Symbol is FuMethod method;
WriteCallExpr(expr.Type, expr.Method.Left, method, expr.Arguments, parent);
}
internal override void VisitSelectExpr!(FuSelectExpr expr, FuPriority parent)
{
WriteCoercedSelect(expr.Type, expr, parent);
}
protected void EnsureChildBlock!()
{
if (this.AtChildStart) {
this.AtLineStart = false;
this.AtChildStart = false;
WriteChar(' ');
OpenBlock();
this.InChildBlock = true;
}
}
protected static bool HasTemporaries(FuExpr expr)
{
switch (expr) {
case FuAggregateInitializer init:
return init.Items.Any(item => HasTemporaries(item));
case FuLiteral:
case FuLambdaExpr:
return false;
case FuInterpolatedString interp:
return interp.Parts.Any(part => HasTemporaries(part.Argument));
case FuSymbolReference symbol:
return symbol.Left != null && HasTemporaries(symbol.Left);
case FuUnaryExpr unary:
return unary.Inner != null && (HasTemporaries(unary.Inner) || unary.Inner is FuAggregateInitializer);
case FuBinaryExpr binary:
return HasTemporaries(binary.Left) || (binary.Op == FuToken.Is ? binary.Right is FuVar : HasTemporaries(binary.Right));
case FuSelectExpr select:
return HasTemporaries(select.Cond) || HasTemporaries(select.OnTrue) || HasTemporaries(select.OnFalse);
case FuCallExpr call:
return HasTemporaries(call.Method) || call.Arguments.Any(arg => HasTemporaries(arg));
default:
assert false;
}
}
protected abstract void StartTemporaryVar!(FuType type);
protected virtual void DefineObjectLiteralTemporary!(FuUnaryExpr expr)
{
if (expr.Inner is FuAggregateInitializer init) {
EnsureChildBlock();
int id = this.CurrentTemporaries.IndexOf(expr.Type);
if (id < 0) {
id = this.CurrentTemporaries.Count;
StartTemporaryVar(expr.Type);
this.CurrentTemporaries.Add(expr);
}
else
this.CurrentTemporaries[id] = expr;
WriteTemporaryName(id);
Write(" = ");
assert expr.Type is FuDynamicPtrType dynamic;
WriteNew(dynamic, FuPriority.Argument);
EndStatement();
foreach (FuExpr item in init.Items) {
WriteTemporaryName(id);
WriteAggregateInitField(expr, item);
}
}
}
protected virtual void WriteTemporariesNotSubstring!(FuExpr expr)
{
WriteTemporaries(expr);
}
protected virtual void WriteOwningTemporary!(FuExpr expr)
{
}
protected virtual void WriteArgTemporary!(FuMethod method, FuVar param, FuExpr arg)
{
}
protected void WriteTemporaries!(FuExpr expr)
{
switch (expr) {
case FuVar def:
if (def.Value != null) {
if (def.Value is FuUnaryExpr unary && unary.Inner is FuAggregateInitializer)
WriteTemporaries(unary.Inner);
else
WriteTemporaries(def.Value);
}
break;
case FuAggregateInitializer init:
foreach (FuExpr item in init.Items) {
assert item is FuBinaryExpr assign;
WriteTemporaries(assign.Right);
}
break;
case FuLiteral:
case FuLambdaExpr:
break;
case FuInterpolatedString interp:
foreach (FuInterpolatedPart part in interp.Parts)
WriteTemporariesNotSubstring(part.Argument);
break;
case FuSymbolReference symbol:
if (symbol.Left != null) {
WriteTemporaries(symbol.Left);
WriteOwningTemporary(symbol.Left);
}
break;
case FuUnaryExpr unary:
if (unary.Inner != null) {
WriteTemporaries(unary.Inner);
DefineObjectLiteralTemporary(unary);
}
break;
case FuBinaryExpr binary:
WriteTemporariesNotSubstring(binary.Left);
if (binary.Op == FuToken.Is)
DefineIsVar(binary);
else {
WriteTemporaries(binary.Right);
if (binary.Op != FuToken.Assign)
WriteOwningTemporary(binary.Right);
}
break;
case FuSelectExpr select:
WriteTemporaries(select.Cond);
WriteTemporaries(select.OnTrue);
WriteTemporaries(select.OnFalse);
break;
case FuCallExpr call:
WriteTemporaries(call.Method);
assert call.Method.Symbol is FuMethod method;
FuVar? param = method.FirstParameter();
foreach (FuExpr arg in call.Arguments) {
WriteTemporaries(arg);
WriteArgTemporary(method, param, arg);
param = param.NextVar();
}
break;
default:
assert false;
}
}
protected virtual void CleanupTemporary!(int i, FuExpr temp)
{
}
protected void CleanupTemporaries!()
{
for (int i = this.CurrentTemporaries.Count; --i >= 0; ) {
FuExpr temp = this.CurrentTemporaries[i];
if (!(temp is FuType)) {
CleanupTemporary(i, temp);
this.CurrentTemporaries[i] = temp.Type;
}
}
}
internal override void VisitExpr!(FuExpr statement)
{
WriteTemporaries(statement);
statement.Accept(this, FuPriority.Statement);
WriteCharLine(';');
if (statement is FuVar def)
WriteInitCode(def);
CleanupTemporaries();
}
internal override void VisitConst!(FuConst statement)
{
}
protected abstract void WriteAssertCast!(FuBinaryExpr expr);
protected abstract void WriteAssert!(FuAssert statement);
internal override void VisitAssert!(FuAssert statement)
{
if (statement.Cond is FuBinaryExpr binary && binary.Op == FuToken.Is && binary.Right is FuVar)
WriteAssertCast(binary);
else
WriteAssert(statement);
}
protected void WriteFirstStatements!(List<FuStatement#> statements, int count)
{
for (int i = 0; i < count; i++)
statements[i].AcceptStatement(this);
}
protected virtual void WriteStatements!(List<FuStatement#> statements)
{
WriteFirstStatements(statements, statements.Count);
}
protected virtual void CleanupBlock!(FuBlock statement)
{
}
protected void TrimTemporariesAndCloseBlock!(int temporariesCount)
{
this.CurrentTemporaries.RemoveRange(temporariesCount, this.CurrentTemporaries.Count - temporariesCount);
CloseBlock();
}
internal override void VisitBlock!(FuBlock statement)
{
if (this.AtChildStart) {
this.AtLineStart = false;
this.AtChildStart = false;
WriteChar(' ');
}
OpenBlock();
int temporariesCount = this.CurrentTemporaries.Count;
WriteStatements(statement.Statements);
CleanupBlock(statement);
TrimTemporariesAndCloseBlock(temporariesCount);
}
protected virtual void WriteChild!(FuStatement! statement)
{
bool wasInChildBlock = this.InChildBlock;
this.AtLineStart = true;
this.AtChildStart = true;
this.InChildBlock = false;
int temporariesCount = this.CurrentTemporaries.Count;
statement.AcceptStatement(this);
if (this.InChildBlock)
TrimTemporariesAndCloseBlock(temporariesCount);
else if (!(statement is FuBlock))
this.Indent--;
this.InChildBlock = wasInChildBlock;
}
protected virtual void StartBreakGoto!()
{
Write("goto fuafterswitch");
}
internal override void VisitBreak!(FuBreak statement)
{
if (statement.LoopOrSwitch is FuSwitch switchStatement) {
int gotoId = this.SwitchesWithGoto.IndexOf(switchStatement);
if (gotoId >= 0) {
StartBreakGoto();
VisitLiteralLong(gotoId, FuPriority.Primary);
WriteCharLine(';');
return;
}
}
WriteLine("break;");
}
internal override void VisitContinue!(FuContinue statement)
{
WriteLine("continue;");
}
internal override void VisitDoWhile!(FuDoWhile statement)
{
Write("do");
WriteChild(statement.Body);
Write("while (");
statement.Cond.Accept(this, FuPriority.Argument);
WriteLine(");");
}
internal override void VisitFor!(FuFor statement)
{
if (statement.Cond != null)
WriteTemporaries(statement.Cond);
Write("for (");
if (statement.Init != null)
statement.Init.Accept(this, FuPriority.Statement);
WriteChar(';');
if (statement.Cond != null) {
WriteChar(' ');
statement.Cond.Accept(this, FuPriority.Argument);
}
WriteChar(';');
if (statement.Advance != null) {
WriteChar(' ');
statement.Advance.Accept(this, FuPriority.Statement);
}
WriteChar(')');
WriteChild(statement.Body);
}
protected void WriteTryParseFailure!(FuCallExpr call, FuStatement!? onFailure)
{
if (!(onFailure is FuReturn || onFailure is FuThrow)) {
call.Method.Left.Accept(this, FuPriority.Assign);
Write(" = 0");
EndStatement();
}
if (onFailure != null)
FlattenBlock(onFailure);
}
protected void EndTryParse!(FuCallExpr call, FuStatement!? onParsed, string statement, FuStatement!? onFailure)
{
if (onParsed != null)
FlattenBlock(onParsed);
CloseBlock();
Write(statement);
OpenBlock();
WriteTryParseFailure(call, onFailure);
CloseBlock();
}
protected virtual void WriteTryParseStatement!(FuCallExpr call, FuStatement!? onParsed, FuStatement!? onFailure)
{
assert false;
}
protected bool TryWriteTryParse!(FuExpr expr, FuStatement!? onParsed, FuStatement!? onFailure)
{
if (expr is FuCallExpr call) {
switch (call.Method.Symbol.Id) {
case FuId.IntTryParse:
case FuId.NIntTryParse:
case FuId.LongTryParse:
case FuId.FloatTryParse:
case FuId.DoubleTryParse:
WriteTryParseStatement(call, onParsed, onFailure);
return true;
default:
break;
}
}
return false;
}
internal bool TryWriteIfTryParse!(FuIf statement)
{
if (statement.Cond is FuPrefixExpr not && not.Op == FuToken.ExclamationMark)
return TryWriteTryParse(not.Inner, statement.OnFalse, statement.OnTrue);
return TryWriteTryParse(statement.Cond, statement.OnTrue, statement.OnFalse);
}
protected virtual bool EmbedIfWhileIsVar!(FuExpr expr, bool write) => false;
void StartIfWhile!(FuExpr expr)
{
EmbedIfWhileIsVar(expr, true);
expr.Accept(this, FuPriority.Argument);
WriteChar(')');
}
protected virtual void StartIf!(FuExpr expr)
{
Write("if (");
StartIfWhile(expr);
}
void WriteIf!(FuIf statement)
{
StartIf(statement.Cond);
WriteChild(statement.OnTrue);
if (statement.OnFalse != null) {
Write("else");
if (statement.OnFalse is FuIf! elseIf) {
bool wasInChildBlock = this.InChildBlock;
this.AtLineStart = true;
this.AtChildStart = true;
this.InChildBlock = false;
if (!EmbedIfWhileIsVar(elseIf.Cond, false)) // FIXME: IsVar but not object literal
WriteTemporaries(elseIf.Cond);
if (this.InChildBlock) {
WriteIf(elseIf);
CloseBlock();
}
else {
this.AtLineStart = false;
this.AtChildStart = false;
WriteChar(' ');
WriteIf(elseIf);
}
this.InChildBlock = wasInChildBlock;
}
else
WriteChild(statement.OnFalse);
}
}
internal override void VisitIf!(FuIf statement)
{
if (!EmbedIfWhileIsVar(statement.Cond, false)) // FIXME: IsVar but not object literal
WriteTemporaries(statement.Cond);
WriteIf(statement);
}
internal override void VisitNative!(FuNative statement)
{
Write(statement.Content);
}
internal override void VisitReturn!(FuReturn statement)
{
if (statement.Value == null)
WriteLine("return;");
else {
WriteTemporaries(statement.Value);
Write("return ");
WriteStronglyCoerced(this.CurrentMethod.Type, statement.Value);
WriteCharLine(';');
CleanupTemporaries();
}
}
protected void DefineVar!(FuExpr value)
{
if (value is FuVar def) {
WriteVar(def);
EndStatement();
}
}
protected void WriteSwitchLabel!(FuSwitch statement)
{
Write("fuswitch");
VisitLiteralLong(this.SwitchesWithGoto.Count, FuPriority.Primary);
this.SwitchesWithGoto.Add(statement);
Write(": ");
}
protected virtual void WriteSwitchCaseTypeVar!(FuExpr value)
{
}
protected virtual void WriteSwitchValue!(FuExpr expr)
{
expr.Accept(this, FuPriority.Argument);
}
protected virtual void WriteSwitchCaseValue!(FuSwitch statement, FuExpr value)
{
if (value is FuBinaryExpr when1 && when1.Op == FuToken.When) {
WriteSwitchCaseValue(statement, when1.Left);
Write(" when ");
when1.Right.Accept(this, FuPriority.Argument);
}
else
WriteCoercedLiteral(statement.Value.Type, value);
}
protected virtual void WriteSwitchCaseBody!(List<FuStatement#> statements)
{
WriteStatements(statements);
}
protected virtual void WriteSwitchCase!(FuSwitch statement, FuCase kase)
{
foreach (FuExpr value in kase.Values) {
Write("case ");
WriteSwitchCaseValue(statement, value);
WriteCharLine(':');
}
this.Indent++;
WriteSwitchCaseBody(kase.Body);
this.Indent--;
}
protected void StartSwitch!(FuSwitch statement)
{
Write("switch (");
WriteSwitchValue(statement.Value);
WriteLine(") {");
foreach (FuCase kase in statement.Cases)
WriteSwitchCase(statement, kase);
}
protected virtual bool NeedsSwitchVar(FuExpr expr) => !expr.IsSimple();
protected virtual void WriteSwitchVar!(FuExpr expr)
{
StartTemporaryVar(expr.Type);
Write("fuSwitchValue = ");
expr.Accept(this, FuPriority.Argument);
}
protected void WriteExprOrSwitchValue!(bool switchVar, FuExpr expr, FuPriority parent)
{
if (switchVar)
Write("fuSwitchValue");
else
expr.Accept(this, parent);
}
protected virtual void WriteSwitchCaseCond!(bool switchVar, FuExpr switchValue, FuExpr value, FuPriority parent)
{
if (value is FuBinaryExpr when1 && when1.Op == FuToken.When) {
if (parent > FuPriority.SelectCond)
WriteChar('(');
WriteSwitchCaseCond(switchVar, switchValue, when1.Left, FuPriority.CondAnd);
Write(" && ");
when1.Right.Accept(this, FuPriority.CondAnd);
if (parent > FuPriority.SelectCond)
WriteChar(')');
}
else
WriteEqual(switchVar ? null : switchValue, value, parent, false);
}
protected virtual void WriteIfCaseBody!(List<FuStatement#> body, bool doWhile, FuSwitch statement, FuCase? kase)
{
int length = FuSwitch.LengthWithoutTrailingBreak(body);
if (doWhile && FuSwitch.HasEarlyBreak(body)) {
this.Indent++;
WriteNewLine();
Write("do ");
OpenBlock();
WriteFirstStatements(body, length);
CloseBlock();
WriteLine("while (false);");
this.Indent--;
}
else if (length != 1 || body[0] is FuIf || body[0] is FuSwitch /* potentially as ifs */) {
WriteChar(' ');
OpenBlock();
WriteFirstStatements(body, length);
CloseBlock();
}
else
WriteChild(body[0]);
}
protected void WriteSwitchAsIfs!(FuSwitch statement, bool doWhile)
{
bool switchVar = NeedsSwitchVar(statement.Value);
if (switchVar) {
OpenBlock();
WriteSwitchVar(statement.Value);
WriteCharLine(';');
}
foreach (FuCase kase in statement.Cases) {
foreach (FuExpr value in kase.Values) {
if (value is FuBinaryExpr when1 && when1.Op == FuToken.When) {
DefineVar(when1.Left);
WriteTemporaries(when1);
}
else
WriteSwitchCaseTypeVar(value);
}
}
string op = "if (";
foreach (FuCase kase in statement.Cases) {
FuPriority parent = kase.Values.Count == 1 ? FuPriority.Argument : FuPriority.CondOr;
foreach (FuExpr value in kase.Values) {
Write(op);
WriteSwitchCaseCond(switchVar, statement.Value, value, parent);
op = " || ";
}
WriteChar(')');
WriteIfCaseBody(kase.Body, doWhile, statement, kase);
op = "else if (";
}
if (statement.HasDefault()) {
Write("else");
WriteIfCaseBody(statement.DefaultBody, doWhile, statement, null);
}
if (switchVar)
CloseBlock();
}
internal override void VisitSwitch!(FuSwitch statement)
{
WriteTemporaries(statement.Value);
StartSwitch(statement);
if (statement.DefaultBody.Count > 0) {
WriteLine("default:");
this.Indent++;
WriteSwitchCaseBody(statement.DefaultBody);
this.Indent--;
}
WriteCharLine('}');
}
protected virtual void WriteException!()
{
Write("Exception");
}
protected void WriteExceptionClass!(FuSymbol klass)
{
if (klass.Name == "Exception")
WriteException();
else
WriteName(klass);
}
protected virtual void WriteThrowMessage!(FuExpr? expr)
{
if (expr != null)
expr.Accept(this, FuPriority.Argument);
}
protected void WriteThrowArgument!(FuThrow statement)
{
WriteExceptionClass(statement.Class.Symbol);
WriteChar('(');
WriteThrowMessage(statement.Message);
WriteChar(')');
}
internal override void VisitThrow!(FuThrow statement)
{
Write("throw new ");
WriteThrowArgument(statement);
WriteCharLine(';');
}
internal override void VisitWhile!(FuWhile statement)
{
if (!EmbedIfWhileIsVar(statement.Cond, false)) // FIXME: IsVar but not object literal
WriteTemporaries(statement.Cond);
Write("while (");
StartIfWhile(statement.Cond);
WriteChild(statement.Body);
}
protected void FlattenBlock!(FuStatement! statement)
{
if (statement is FuBlock block)
WriteStatements(block.Statements);
else
statement.AcceptStatement(this);
}
protected virtual bool HasInitCode(FuNamedValue def) => GetAggregateInitializer(def) != null;
protected virtual bool NeedsConstructor(FuClass klass)
{
for (FuSymbol? symbol = klass.First; symbol != null; symbol = symbol.Next) {
if (symbol is FuField field && HasInitCode(field))
return true;
}
return klass.Constructor != null;
}
protected virtual void WriteInitField!(FuField field)
{
WriteInitCode(field);
}
protected void WriteConstructorBody!(FuClass klass)
{
for (FuSymbol? symbol = klass.First; symbol != null; symbol = symbol.Next) {
if (symbol is FuField field)
WriteInitField(field);
}
if (klass.Constructor != null) {
this.CurrentMethod = klass.Constructor;
assert klass.Constructor.Body is FuBlock block;
WriteStatements(block.Statements);
this.CurrentMethod = null;
}
this.SwitchesWithGoto.Clear();
this.CurrentTemporaries.Clear();
}
protected virtual void WriteParameter!(FuVar param)
{
WriteTypeAndName(param);
}
protected void WriteRemainingParameters!(FuMethod method, bool first, bool defaultArguments)
{
for (FuVar? param = method.FirstParameter(); param != null; param = param.NextVar()) {
if (!first)
Write(", ");
first = false;
WriteParameter(param);
if (defaultArguments)
WriteVarInit(param);
}
WriteChar(')');
}
protected void WriteParameters!(FuMethod method, bool defaultArguments)
{
WriteChar('(');
WriteRemainingParameters(method, true, defaultArguments);
}
protected virtual bool IsShortMethod(FuMethod method) => false;
protected void WriteBody!(FuMethod method)
{
if (method.CallType == FuCallType.Abstract)
WriteCharLine(';');
else {
this.CurrentMethod = method;
if (IsShortMethod(method)) {
Write(" => ");
assert method.Body is FuReturn ret;
WriteCoerced(method.Type, ret.Value, FuPriority.Argument);
WriteCharLine(';');
}
else {
WriteNewLine();
OpenBlock();
FlattenBlock(method.Body);
CloseBlock();
}
this.CurrentMethod = null;
}
}
protected void WritePublic!(FuContainerType container)
{
if (container.IsPublic)
Write("public ");
}
protected void WriteEnumValue!(FuConst konst, bool withValue)
{
WriteDoc(konst.Documentation);
WriteName(konst);
if (withValue && !(konst.Value is FuImplicitEnumValue)) {
Write(" = ");
konst.Value.Accept(this, FuPriority.Argument);
}
}
internal override void VisitEnumValue!(FuConst konst, FuConst? previous)
{
if (previous != null)
WriteCharLine(',');
WriteEnumValue(konst, true);
}
protected abstract void WriteEnum!(FuEnum enu);
protected virtual void WriteRegexOptionsEnum!(FuProgram program)
{
if (program.RegexOptionsEnum)
WriteEnum(program.System.RegexOptionsEnum);
}
protected void StartClass!(FuClass klass, string suffix, string extendsClause)
{
Write("class ");
Write(klass.Name);
Write(suffix);
if (klass.HasBaseClass()) {
Write(extendsClause);
WriteExceptionClass(klass.Parent);
}
}
protected void OpenClass!(FuClass klass, string suffix, string extendsClause)
{
StartClass(klass, suffix, extendsClause);
WriteNewLine();
OpenBlock();
}
protected abstract void WriteConst!(FuConst konst);
protected abstract void WriteField!(FuField field);
protected abstract void WriteMethod!(FuMethod method);
protected void WriteMembers!(FuClass klass, bool constArrays)
{
for (FuSymbol? symbol = klass.First; symbol != null; symbol = symbol.Next) {
switch (symbol) {
case FuConst konst:
WriteConst(konst);
break;
case FuField field:
WriteField(field);
break;
case FuMethod method:
WriteMethod(method);
this.SwitchesWithGoto.Clear();
this.CurrentTemporaries.Clear();
break;
case FuNative nat:
VisitNative(nat);
break;
default:
assert false;
}
}
if (constArrays) {
foreach (FuConst konst in klass.ConstArrays)
WriteConst(konst);
}
}
protected bool WriteBaseClass!(FuClass klass, FuProgram program)
{
if (klass.Name == "Exception")
return false;
// topological sorting of class hierarchy
if (this.WrittenTypes.Contains(klass))
return false;
this.WrittenTypes.Add(klass);
if (klass.Parent is FuClass baseClass)
WriteClass(baseClass, program);
return true;
}
protected abstract void WriteClass!(FuClass klass, FuProgram program);
protected void WriteTypes!(FuProgram program)
{
WriteRegexOptionsEnum(program);
for (FuSymbol? type = program.First; type != null; type = type.Next) {
switch (type) {
case FuClass klass:
WriteClass(klass, program);
break;
case FuEnum enu:
WriteEnum(enu);
break;
default:
assert false;
}
}
}
public abstract void WriteProgram!(FuProgram program, string outputFile, string namespace);
}