This repository has been archived on 2025-10-23. You can view files and clone it, but cannot push or open issues or pull requests.
Files
nub-lang-archive/src/lang/Nub.Lang/Backend/Generator.cs
nub31 c60ae5d092 ...
2025-05-27 10:22:51 +02:00

1514 lines
60 KiB
C#

using System.Globalization;
using System.Text;
using Nub.Lang.Frontend.Parsing;
using Nub.Lang.Frontend.Typing;
namespace Nub.Lang.Backend;
public class Generator
{
private List<SourceFile> _sourceFiles = [];
private StringBuilder _builder = new();
private Dictionary<string, Variable> _variables = [];
private List<string> _strings = [];
private Stack<string> _breakLabels = [];
private Stack<string> _continueLabels = [];
private int _variableIndex;
private int _labelIndex;
private int _funcIndex;
private bool _codeIsReachable = true;
private Dictionary<IFuncSignature, string> _funcNames = [];
public string Generate(List<SourceFile> sourceFiles)
{
_sourceFiles = sourceFiles;
_builder = new StringBuilder();
_variables = new Dictionary<string, Variable>();
_strings = [];
_funcNames = [];
_breakLabels = [];
_continueLabels = [];
_variableIndex = 0;
_funcIndex = 0;
_labelIndex = 0;
_codeIsReachable = true;
foreach (var structDef in _sourceFiles.SelectMany(f => f.Definitions).OfType<StructDefinitionNode>())
{
GenerateStructDefinition(structDef);
_builder.AppendLine();
}
foreach (var funcSignature in _sourceFiles.SelectMany(f => f.Definitions).OfType<IFuncSignature>())
{
switch (funcSignature)
{
case ExternFuncDefinitionNode externFuncDefinitionNode:
_funcNames[funcSignature] = externFuncDefinitionNode.Name;
break;
case LocalFuncDefinitionNode localFuncDefinitionNode:
if (localFuncDefinitionNode.Exported)
{
_funcNames[funcSignature] = localFuncDefinitionNode.Name;
}
else
{
var funcName = GenFuncName();
_funcNames[funcSignature] = funcName;
}
break;
default:
throw new ArgumentOutOfRangeException(nameof(funcSignature));
}
}
foreach (var funcDef in _sourceFiles.SelectMany(f => f.Definitions).OfType<LocalFuncDefinitionNode>())
{
GenerateFuncDefinition(funcDef);
_builder.AppendLine();
}
for (var i = 0; i < _strings.Count; i++)
{
var str = _strings[i];
_builder.AppendLine($"data $str{i + 1} = {{ b \"{str}\", b 0 }}");
}
return _builder.ToString();
}
private static string SQT(NubType type)
{
switch (type)
{
case NubPrimitiveType primitiveType:
{
switch (primitiveType.Kind)
{
case PrimitiveTypeKind.I64:
case PrimitiveTypeKind.U64:
case PrimitiveTypeKind.String:
case PrimitiveTypeKind.Any:
return "l";
case PrimitiveTypeKind.I32:
case PrimitiveTypeKind.U32:
case PrimitiveTypeKind.I16:
case PrimitiveTypeKind.U16:
case PrimitiveTypeKind.I8:
case PrimitiveTypeKind.U8:
case PrimitiveTypeKind.Bool:
return "w";
case PrimitiveTypeKind.F64:
return "d";
case PrimitiveTypeKind.F32:
return "s";
default:
throw new ArgumentOutOfRangeException();
}
}
case NubStructType:
case NubPointerType:
case NubArrayType:
{
return "l";
}
default:
{
throw new NotImplementedException();
}
}
}
private static string EQT(NubType type)
{
switch (type)
{
case NubPrimitiveType primitiveType:
{
switch (primitiveType.Kind)
{
case PrimitiveTypeKind.I64:
case PrimitiveTypeKind.U64:
case PrimitiveTypeKind.String:
return "l";
case PrimitiveTypeKind.Any:
case PrimitiveTypeKind.I32:
case PrimitiveTypeKind.U32:
return "w";
case PrimitiveTypeKind.I16:
case PrimitiveTypeKind.U16:
return "h";
case PrimitiveTypeKind.I8:
case PrimitiveTypeKind.U8:
return "b";
case PrimitiveTypeKind.Bool:
case PrimitiveTypeKind.F64:
return "d";
case PrimitiveTypeKind.F32:
return "s";
default:
throw new ArgumentOutOfRangeException();
}
}
case NubStructType nubCustomType:
{
return ":" + nubCustomType.Name;
}
case NubPointerType:
case NubArrayType:
{
return "l";
}
default:
{
throw new NotImplementedException();
}
}
}
private static string FQT(NubType type)
{
switch (type)
{
case NubPrimitiveType primitiveType:
{
switch (primitiveType.Kind)
{
case PrimitiveTypeKind.I64:
case PrimitiveTypeKind.U64:
case PrimitiveTypeKind.String:
case PrimitiveTypeKind.Any:
return "l";
case PrimitiveTypeKind.I32:
case PrimitiveTypeKind.U32:
return "w";
case PrimitiveTypeKind.I16:
return "sh";
case PrimitiveTypeKind.U16:
return "uh";
case PrimitiveTypeKind.I8:
return "sb";
case PrimitiveTypeKind.U8:
return "ub";
case PrimitiveTypeKind.Bool:
return "b";
case PrimitiveTypeKind.F64:
return "d";
case PrimitiveTypeKind.F32:
return "s";
default:
throw new ArgumentOutOfRangeException();
}
}
case NubStructType nubCustomType:
{
return ":" + nubCustomType.Name;
}
case NubPointerType:
case NubArrayType:
{
return "l";
}
default:
{
throw new NotImplementedException();
}
}
}
private int QbeTypeSize(NubType type)
{
switch (type)
{
case NubPrimitiveType primitiveType:
{
switch (primitiveType.Kind)
{
case PrimitiveTypeKind.I64:
case PrimitiveTypeKind.U64:
case PrimitiveTypeKind.String:
case PrimitiveTypeKind.Any:
return 8;
case PrimitiveTypeKind.I32:
case PrimitiveTypeKind.U32:
return 4;
case PrimitiveTypeKind.I16:
case PrimitiveTypeKind.U16:
return 2;
case PrimitiveTypeKind.I8:
case PrimitiveTypeKind.U8:
case PrimitiveTypeKind.Bool:
return 1;
case PrimitiveTypeKind.F64:
return 8;
case PrimitiveTypeKind.F32:
return 4;
default:
throw new ArgumentOutOfRangeException();
}
}
case NubStructType nubStructType:
{
var definition = LookupStructDefinition(nubStructType.Namespace, nubStructType.Name);
if (definition == null)
{
throw new Exception($"Cannot determine size of non-existent type {nubStructType}");
}
return definition.Fields.Sum(f => QbeTypeSize(f.Type));
}
case NubPointerType:
case NubArrayType:
{
return 8;
}
default:
{
throw new NotImplementedException();
}
}
}
private void GenerateFuncDefinition(LocalFuncDefinitionNode node)
{
_variables.Clear();
if (node.Exported)
{
_builder.Append("export ");
}
_builder.Append("function ");
if (node.ReturnType.HasValue)
{
_builder.Append($"{FQT(node.ReturnType.Value)} ");
}
else if (!node.ReturnType.HasValue && node.Name == "main")
{
_builder.Append("l ");
}
_builder.Append('$');
_builder.Append(_funcNames[node]);
var parameterStrings = node.Parameters.Select(parameter => parameter.Variadic ? "..." : $"{FQT(parameter.Type)} %{parameter.Name}");
_builder.AppendLine($"({string.Join(", ", parameterStrings)}) {{");
_builder.AppendLine("@start");
_builder.AppendLine(" # Variable allocation");
foreach (var parameter in node.Parameters)
{
var parameterName = parameter.Name;
switch (FQT(parameter.Type))
{
case "sb":
parameterName = GenVarName();
_builder.AppendLine($" %{parameterName} =w extsb %{parameter.Name}");
break;
case "ub":
parameterName = GenVarName();
_builder.AppendLine($" %{parameterName} =w extub %{parameter.Name}");
break;
case "sh":
parameterName = GenVarName();
_builder.AppendLine($" %{parameterName} =w extsh %{parameter.Name}");
break;
case "uh":
parameterName = GenVarName();
_builder.AppendLine($" %{parameterName} =w extuh %{parameter.Name}");
break;
}
var pointerName = GenVarName();
_builder.AppendLine($" %{pointerName} ={SQT(parameter.Type)} alloc8 {QbeTypeSize(parameter.Type)}");
_builder.AppendLine($" storel %{parameterName}, %{pointerName}");
_variables[parameter.Name] = new Variable
{
Pointer = $"%{pointerName}",
Type = parameter.Type
};
}
_builder.AppendLine(" # End variable allocation");
_builder.AppendLine();
GenerateBlock(node.Body);
if (node.Body.Statements.LastOrDefault() is not ReturnNode)
{
if (!node.ReturnType.HasValue && node.Name == "main")
{
_builder.AppendLine();
_builder.AppendLine(" # Implicit return for main");
_builder.AppendLine(" ret 0");
}
else if (!node.ReturnType.HasValue)
{
_builder.AppendLine();
_builder.AppendLine(" # Implicit return");
_builder.AppendLine(" ret");
}
}
_builder.AppendLine("}");
}
private void GenerateStructDefinition(StructDefinitionNode structDefinition)
{
var fields = structDefinition.Fields.Select(f => EQT(f.Type));
_builder.AppendLine($"type :{structDefinition.Name} = {{ {string.Join(", ", fields)} }}");
}
private void GenerateStatement(StatementNode statement)
{
switch (statement)
{
case BreakNode:
GenerateBreak();
break;
case ContinueNode:
GenerateContinue();
break;
case FuncCallStatementNode funcCallStatement:
GenerateStatementFuncCall(funcCallStatement);
break;
case IfNode ifStatement:
GenerateIf(ifStatement);
break;
case ReturnNode @return:
GenerateReturn(@return);
break;
case VariableAssignmentNode variableAssignment:
GenerateVariableAssignment(variableAssignment);
break;
case WhileNode whileStatement:
GenerateWhile(whileStatement);
break;
default:
throw new ArgumentOutOfRangeException(nameof(statement));
}
}
private string GenerateFuncCall(FuncCall funcCall)
{
var funcDefinition = LookupFuncSignature(funcCall.Namespace, funcCall.Name, funcCall.Parameters.Select(p => p.Type).ToList());
if (funcDefinition == null)
{
throw new Exception($"Unknown function {funcCall}");
}
var parameterStrings = new List<string>();
for (var i = 0; i < funcCall.Parameters.Count; i++)
{
if (i < funcDefinition.Parameters.Count && funcDefinition.Parameters[i].Variadic)
{
parameterStrings.Add("...");
}
NubType expectedType;
if (i < funcDefinition.Parameters.Count)
{
expectedType = funcDefinition.Parameters[i].Type;
}
else if (funcDefinition.Parameters[^1].Variadic)
{
expectedType = funcDefinition.Parameters[^1].Type;
}
else
{
throw new Exception($"Parameters for func {funcCall} does not not match");
}
var parameter = funcCall.Parameters[i];
var result = GenerateExpression(parameter);
var qbeParameterType = SQT(expectedType.Equals(NubPrimitiveType.Any) ? parameter.Type : expectedType);
parameterStrings.Add($"{qbeParameterType} {result}");
}
var funcName = _funcNames[funcDefinition];
return $"call ${funcName}({string.Join(", ", parameterStrings)})";
}
private void GenerateBlock(BlockNode block)
{
foreach (var statement in block.Statements.Where(_ => _codeIsReachable))
{
GenerateStatement(statement);
}
_codeIsReachable = true;
}
private void GenerateBreak()
{
_builder.AppendLine($" jmp @{_breakLabels.Peek()}");
_codeIsReachable = false;
}
private void GenerateContinue()
{
_builder.AppendLine($" jmp @{_continueLabels.Peek()}");
_codeIsReachable = false;
}
private void GenerateStatementFuncCall(FuncCallStatementNode funcCall)
{
var call = GenerateFuncCall(funcCall.FuncCall);
_builder.AppendLine($" {call}");
}
private void GenerateIf(IfNode ifStatement)
{
var trueLabel = GenLabelName();
var falseLabel = GenLabelName();
var endLabel = GenLabelName();
var result = GenerateExpression(ifStatement.Condition);
_builder.AppendLine($" jnz {result}, @{trueLabel}, @{falseLabel}");
_builder.AppendLine($"@{trueLabel}");
GenerateBlock(ifStatement.Body);
_builder.AppendLine($" jmp @{endLabel}");
_builder.AppendLine($"@{falseLabel}");
if (ifStatement.Else.HasValue)
{
ifStatement.Else.Value.Match
(
GenerateIf,
GenerateBlock
);
}
_builder.AppendLine($"@{endLabel}");
}
private void GenerateReturn(ReturnNode @return)
{
if (@return.Value.HasValue)
{
var result = GenerateExpression(@return.Value.Value);
_builder.AppendLine($" ret {result}");
}
else
{
_builder.AppendLine(" ret");
}
}
private void GenerateVariableAssignment(VariableAssignmentNode variableAssignment)
{
var result = GenerateExpression(variableAssignment.Value);
if (_variables.TryGetValue(variableAssignment.Name, out var existingVariable))
{
_builder.AppendLine($" storel {result}, {existingVariable.Pointer}");
}
else
{
var pointerName = GenVarName();
_builder.AppendLine($" %{pointerName} ={SQT(variableAssignment.Value.Type)} alloc8 {QbeTypeSize(variableAssignment.Value.Type)}");
_builder.AppendLine($" storel {result}, %{pointerName}");
_variables[variableAssignment.Name] = new Variable
{
Pointer = $"%{pointerName}",
Type = variableAssignment.Value.Type
};
}
}
private void GenerateWhile(WhileNode whileStatement)
{
var conditionLabel = GenLabelName();
var iterationLabel = GenLabelName();
var endLabel = GenLabelName();
_breakLabels.Push(endLabel);
_continueLabels.Push(conditionLabel);
_builder.AppendLine($" jmp @{conditionLabel}");
_builder.AppendLine($"@{iterationLabel}");
GenerateBlock(whileStatement.Body);
_builder.AppendLine($"@{conditionLabel}");
var result = GenerateExpression(whileStatement.Condition);
_builder.AppendLine($" jnz {result}, @{iterationLabel}, @{endLabel}");
_builder.AppendLine($"@{endLabel}");
_continueLabels.Pop();
_breakLabels.Pop();
}
private string GenerateExpression(ExpressionNode expression)
{
return expression switch
{
AddressOfNode addressOf => GenerateAddressOf(addressOf),
ArrayIndexNode arrayIndex => GenerateArrayIndex(arrayIndex),
BinaryExpressionNode binaryExpression => GenerateBinaryExpression(binaryExpression),
CastNode cast => GenerateCast(cast),
DereferenceNode dereference => GenerateDereference(dereference),
FuncCallExpressionNode funcCallExpression => GenerateExpressionFuncCall(funcCallExpression),
IdentifierNode identifier => GenerateIdentifier(identifier),
LiteralNode literal => GenerateLiteral(literal),
StructInitializerNode structInitializer => GenerateStructInitializer(structInitializer),
UnaryExpressionNode unaryExpression => GenerateUnaryExpression(unaryExpression),
MemberAccessNode memberAccess => GenerateMemberAccess(memberAccess),
_ => throw new ArgumentOutOfRangeException(nameof(expression))
};
}
private string GenerateArrayIndex(ArrayIndexNode arrayIndex)
{
var array = GenerateExpression(arrayIndex.Expression);
var index = GenerateExpression(arrayIndex.Index);
var arrayBaseType = ((NubArrayType)arrayIndex.Expression.Type).BaseType;
var firstItemPointerName = GenVarName();
_builder.AppendLine($" %{firstItemPointerName} =l add {array}, 8");
var offsetPointerName = GenVarName();
_builder.AppendLine($" %{offsetPointerName} =l mul {index}, {QbeTypeSize(arrayBaseType)}");
var resultPointerName = GenVarName();
_builder.AppendLine($" %{resultPointerName} ={SQT(arrayIndex.Type)} add %{firstItemPointerName}, %{offsetPointerName}");
var outputName = GenVarName();
_builder.AppendLine($" %{outputName} =l load{SQT(arrayBaseType)} %{resultPointerName}");
return $"%{outputName}";
}
private string GenerateDereference(DereferenceNode dereference)
{
var result = GenerateExpression(dereference.Expression);
var outputName = GenVarName();
_builder.AppendLine($" %{outputName} ={SQT(dereference.Type)} load{SQT(dereference.Type)} {result}");
return $"%{outputName}";
}
private string GenerateAddressOf(AddressOfNode addressOf)
{
var result = GenerateExpression(addressOf.Expression);
var outputName = GenVarName();
switch (addressOf.Expression.Type)
{
case NubPointerType:
case NubStructType:
case NubPrimitiveType { Kind: PrimitiveTypeKind.String }:
case NubPrimitiveType { Kind: PrimitiveTypeKind.I64 }:
case NubPrimitiveType { Kind: PrimitiveTypeKind.F64 }:
case NubPrimitiveType { Kind: PrimitiveTypeKind.U64 }:
_builder.AppendLine($" %{outputName} =l alloc8 8");
_builder.AppendLine($" storel {result}, %{outputName}");
return $"%{outputName}";
case NubPrimitiveType { Kind: PrimitiveTypeKind.I32 }:
case NubPrimitiveType { Kind: PrimitiveTypeKind.U32 }:
case NubPrimitiveType { Kind: PrimitiveTypeKind.I16 }:
case NubPrimitiveType { Kind: PrimitiveTypeKind.U16 }:
case NubPrimitiveType { Kind: PrimitiveTypeKind.I8 }:
case NubPrimitiveType { Kind: PrimitiveTypeKind.U8 }:
case NubPrimitiveType { Kind: PrimitiveTypeKind.F32 }:
_builder.AppendLine($" %{outputName} =l alloc8 4");
_builder.AppendLine($" storew {result}, %{outputName}");
return $"%{outputName}";
default:
throw new ArgumentOutOfRangeException();
}
}
private string GenerateBinaryExpression(BinaryExpressionNode binaryExpression)
{
var left = GenerateExpression(binaryExpression.Left);
var right = GenerateExpression(binaryExpression.Right);
var outputName = GenVarName();
switch (binaryExpression.Operator)
{
case BinaryExpressionOperator.Equal:
{
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64))
{
_builder.AppendLine($" %{outputName} =w ceql {left}, {right}");
return $"%{outputName}";
}
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32))
{
_builder.AppendLine($" %{outputName} =w ceqw {left}, {right}");
return $"%{outputName}";
}
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.String))
{
_builder.AppendLine($" %{outputName} =w call $nub_strcmp(l {left}, l {right})");
return $"%{outputName}";
}
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.Bool))
{
_builder.AppendLine($" %{outputName} =w ceqw {left}, {right}");
return $"%{outputName}";
}
break;
}
case BinaryExpressionOperator.NotEqual:
{
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64))
{
_builder.AppendLine($" %{outputName} =w cnel {left}, {right}");
return $"%{outputName}";
}
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32))
{
_builder.AppendLine($" %{outputName} =w cnew {left}, {right}");
return $"%{outputName}";
}
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.String))
{
_builder.AppendLine($" %{outputName} =w call $nub_strcmp(l {left}, l {right})");
_builder.AppendLine($" %{outputName} =w xor %{outputName}, 1");
return $"%{outputName}";
}
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.Bool))
{
_builder.AppendLine($" %{outputName} =w cnew {left}, {right}");
return $"%{outputName}";
}
break;
}
case BinaryExpressionOperator.GreaterThan:
{
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64))
{
_builder.AppendLine($" %{outputName} =w csgtl {left}, {right}");
return $"%{outputName}";
}
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32))
{
_builder.AppendLine($" %{outputName} =w csgtw {left}, {right}");
return $"%{outputName}";
}
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.Bool))
{
_builder.AppendLine($" %{outputName} =w csgtw {left}, {right}");
return $"%{outputName}";
}
break;
}
case BinaryExpressionOperator.GreaterThanOrEqual:
{
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64))
{
_builder.AppendLine($" %{outputName} =w csgel {left}, {right}");
return $"%{outputName}";
}
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32))
{
_builder.AppendLine($" %{outputName} =w csgew {left}, {right}");
return $"%{outputName}";
}
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.Bool))
{
_builder.AppendLine($" %{outputName} =w csgew {left}, {right}");
return $"%{outputName}";
}
break;
}
case BinaryExpressionOperator.LessThan:
{
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64))
{
_builder.AppendLine($" %{outputName} =w csltl {left}, {right}");
return $"%{outputName}";
}
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32))
{
_builder.AppendLine($" %{outputName} =w csltw {left}, {right}");
return $"%{outputName}";
}
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.Bool))
{
_builder.AppendLine($" %{outputName} =w csltw {left}, {right}");
return $"%{outputName}";
}
break;
}
case BinaryExpressionOperator.LessThanOrEqual:
{
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64))
{
_builder.AppendLine($" %{outputName} =w cslel {left}, {right}");
return $"%{outputName}";
}
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32))
{
_builder.AppendLine($" %{outputName} =w cslew {left}, {right}");
return $"%{outputName}";
}
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.Bool))
{
_builder.AppendLine($" %{outputName} =w cslew {left}, {right}");
return $"%{outputName}";
}
break;
}
case BinaryExpressionOperator.Plus:
{
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64))
{
_builder.AppendLine($" %{outputName} =l add {left}, {right}");
return $"%{outputName}";
}
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32))
{
_builder.AppendLine($" %{outputName} =w add {left}, {right}");
return $"%{outputName}";
}
break;
}
case BinaryExpressionOperator.Minus:
{
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64))
{
_builder.AppendLine($" %{outputName} =l sub {left}, {right}");
return $"%{outputName}";
}
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32))
{
_builder.AppendLine($" %{outputName} =w sub {left}, {right}");
return $"%{outputName}";
}
break;
}
case BinaryExpressionOperator.Multiply:
{
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64))
{
_builder.AppendLine($" %{outputName} =l mul {left}, {right}");
return $"%{outputName}";
}
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32))
{
_builder.AppendLine($" %{outputName} =w mul {left}, {right}");
return $"%{outputName}";
}
break;
}
case BinaryExpressionOperator.Divide:
{
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64))
{
_builder.AppendLine($" %{outputName} =l div {left}, {right}");
return $"%{outputName}";
}
if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32))
{
_builder.AppendLine($" %{outputName} =w div {left}, {right}");
return $"%{outputName}";
}
break;
}
default:
{
throw new ArgumentOutOfRangeException();
}
}
throw new NotSupportedException($"Binary operator {binaryExpression.Operator} for types {binaryExpression.Left.Type} and {binaryExpression.Right.Type} not supported");
}
private string GenerateCast(CastNode cast)
{
var input = GenerateExpression(cast.Expression);
var outputType = cast.TargetType;
var inputType = cast.Expression.Type;
if (inputType.Equals(outputType))
{
return input;
}
if (outputType is not NubPrimitiveType primitiveOutputType || inputType is not NubPrimitiveType primitiveInputType)
{
throw new NotSupportedException("Casting is only supported for primitive types");
}
var outputName = GenVarName();
switch (primitiveInputType.Kind)
{
case PrimitiveTypeKind.I64:
switch (primitiveOutputType.Kind)
{
case PrimitiveTypeKind.I64:
case PrimitiveTypeKind.U64:
case PrimitiveTypeKind.I32:
case PrimitiveTypeKind.U32:
case PrimitiveTypeKind.I16:
case PrimitiveTypeKind.U16:
case PrimitiveTypeKind.I8:
case PrimitiveTypeKind.Any:
case PrimitiveTypeKind.U8:
return input;
case PrimitiveTypeKind.F64:
_builder.AppendLine($" %{outputName} =d sltof {input}");
return $"%{outputName}";
case PrimitiveTypeKind.F32:
_builder.AppendLine($" %{outputName} =s sltof {input}");
return $"%{outputName}";
case PrimitiveTypeKind.String:
_builder.AppendLine($" %{outputName} =l call $nub_i64_to_string(l {input})");
return $"%{outputName}";
case PrimitiveTypeKind.Bool:
default:
throw new NotSupportedException($"Casting from {primitiveInputType.Kind} to {primitiveOutputType.Kind} is not supported");
}
case PrimitiveTypeKind.I32:
switch (primitiveOutputType.Kind)
{
case PrimitiveTypeKind.I64:
case PrimitiveTypeKind.U64:
_builder.AppendLine($" %{outputName} =l extsw {input}");
return $"%{outputName}";
case PrimitiveTypeKind.I32:
case PrimitiveTypeKind.U32:
case PrimitiveTypeKind.I16:
case PrimitiveTypeKind.U16:
case PrimitiveTypeKind.I8:
case PrimitiveTypeKind.U8:
return input;
case PrimitiveTypeKind.F64:
var extName = GenVarName();
_builder.AppendLine($" %{extName} =l extsw {input}");
_builder.AppendLine($" %{outputName} =d sltof %{extName}");
return $"%{outputName}";
case PrimitiveTypeKind.F32:
_builder.AppendLine($" %{outputName} =s swtof {input}");
return $"%{outputName}";
case PrimitiveTypeKind.String:
_builder.AppendLine($" %{outputName} =l call $nub_i32_to_string(w {input})");
return $"%{outputName}";
case PrimitiveTypeKind.Any:
var extAnyName = GenVarName();
_builder.AppendLine($" %{extAnyName} =l extsw {input}");
return $"%{extAnyName}";
case PrimitiveTypeKind.Bool:
default:
throw new NotSupportedException($"Casting from {primitiveInputType.Kind} to {primitiveOutputType.Kind} is not supported");
}
case PrimitiveTypeKind.I16:
switch (primitiveOutputType.Kind)
{
case PrimitiveTypeKind.I64:
case PrimitiveTypeKind.U64:
_builder.AppendLine($" %{outputName} =l extsh {input}");
return $"%{outputName}";
case PrimitiveTypeKind.I32:
case PrimitiveTypeKind.U32:
_builder.AppendLine($" %{outputName} =w extsh {input}");
return $"%{outputName}";
case PrimitiveTypeKind.I16:
case PrimitiveTypeKind.U16:
case PrimitiveTypeKind.I8:
case PrimitiveTypeKind.U8:
return input;
case PrimitiveTypeKind.F64:
{
var extName = GenVarName();
_builder.AppendLine($" %{extName} =l extsh {input}");
_builder.AppendLine($" %{outputName} =d sltof %{extName}");
return $"%{outputName}";
}
case PrimitiveTypeKind.F32:
{
var extName = GenVarName();
_builder.AppendLine($" %{extName} =w extsh {input}");
_builder.AppendLine($" %{outputName} =s swtof %{extName}");
return $"%{outputName}";
}
case PrimitiveTypeKind.String:
{
var extName = GenVarName();
_builder.AppendLine($" %{extName} =w extsh {input}");
_builder.AppendLine($" %{outputName} =l call $nub_i32_to_string(w %{extName})");
return $"%{outputName}";
}
case PrimitiveTypeKind.Any:
{
var extName = GenVarName();
_builder.AppendLine($" %{extName} =l extsh {input}");
return $"%{extName}";
}
case PrimitiveTypeKind.Bool:
default:
throw new NotSupportedException($"Casting from {primitiveInputType.Kind} to {primitiveOutputType.Kind} is not supported");
}
case PrimitiveTypeKind.I8:
switch (primitiveOutputType.Kind)
{
case PrimitiveTypeKind.I64:
case PrimitiveTypeKind.U64:
_builder.AppendLine($" %{outputName} =l extsb {input}");
return $"%{outputName}";
case PrimitiveTypeKind.I32:
case PrimitiveTypeKind.U32:
case PrimitiveTypeKind.I16:
case PrimitiveTypeKind.U16:
_builder.AppendLine($" %{outputName} =w extsb {input}");
return $"%{outputName}";
case PrimitiveTypeKind.I8:
case PrimitiveTypeKind.U8:
return input;
case PrimitiveTypeKind.F64:
{
var extName = GenVarName();
_builder.AppendLine($" %{extName} =l extsb {input}");
_builder.AppendLine($" %{outputName} =d sltof %{extName}");
return $"%{outputName}";
}
case PrimitiveTypeKind.F32:
{
var extName = GenVarName();
_builder.AppendLine($" %{extName} =w extsb {input}");
_builder.AppendLine($" %{outputName} =s swtof %{extName}");
return $"%{outputName}";
}
case PrimitiveTypeKind.String:
{
var extName = GenVarName();
_builder.AppendLine($" %{extName} =w extsb {input}");
_builder.AppendLine($" %{outputName} =l call $nub_i32_to_string(w %{extName})");
return $"%{outputName}";
}
case PrimitiveTypeKind.Any:
{
var extName = GenVarName();
_builder.AppendLine($" %{extName} =l extsb {input}");
return $"%{extName}";
}
case PrimitiveTypeKind.Bool:
default:
throw new NotSupportedException($"Casting from {primitiveInputType.Kind} to {primitiveOutputType.Kind} is not supported");
}
case PrimitiveTypeKind.U64:
switch (primitiveOutputType.Kind)
{
case PrimitiveTypeKind.I64:
case PrimitiveTypeKind.U64:
case PrimitiveTypeKind.I32:
case PrimitiveTypeKind.U32:
case PrimitiveTypeKind.I16:
case PrimitiveTypeKind.U16:
case PrimitiveTypeKind.I8:
case PrimitiveTypeKind.U8:
return input;
case PrimitiveTypeKind.F64:
_builder.AppendLine($" %{outputName} =d ultof {input}");
return $"%{outputName}";
case PrimitiveTypeKind.F32:
_builder.AppendLine($" %{outputName} =s ultof {input}");
return $"%{outputName}";
case PrimitiveTypeKind.String:
_builder.AppendLine($" %{outputName} =l call $nub_u64_to_string(l {input})");
return $"%{outputName}";
case PrimitiveTypeKind.Any:
return input;
case PrimitiveTypeKind.Bool:
default:
throw new NotSupportedException($"Casting from {primitiveInputType.Kind} to {primitiveOutputType.Kind} is not supported");
}
case PrimitiveTypeKind.U32:
switch (primitiveOutputType.Kind)
{
case PrimitiveTypeKind.I64:
case PrimitiveTypeKind.U64:
_builder.AppendLine($" %{outputName} =l extuw {input}");
return $"%{outputName}";
case PrimitiveTypeKind.I32:
case PrimitiveTypeKind.U32:
case PrimitiveTypeKind.I16:
case PrimitiveTypeKind.U16:
case PrimitiveTypeKind.I8:
case PrimitiveTypeKind.U8:
return input;
case PrimitiveTypeKind.F64:
var extName = GenVarName();
_builder.AppendLine($" %{extName} =l extuw {input}");
_builder.AppendLine($" %{outputName} =d ultof %{extName}");
return $"%{outputName}";
case PrimitiveTypeKind.F32:
_builder.AppendLine($" %{outputName} =s uwtof {input}");
return $"%{outputName}";
case PrimitiveTypeKind.String:
_builder.AppendLine($" %{outputName} =l call $nub_u32_to_string(w {input})");
return $"%{outputName}";
case PrimitiveTypeKind.Any:
var extAnyName = GenVarName();
_builder.AppendLine($" %{extAnyName} =l extuw {input}");
return $"%{extAnyName}";
case PrimitiveTypeKind.Bool:
default:
throw new NotSupportedException($"Casting from {primitiveInputType.Kind} to {primitiveOutputType.Kind} is not supported");
}
case PrimitiveTypeKind.U16:
switch (primitiveOutputType.Kind)
{
case PrimitiveTypeKind.I64:
case PrimitiveTypeKind.U64:
_builder.AppendLine($" %{outputName} =l extuh {input}");
return $"%{outputName}";
case PrimitiveTypeKind.I32:
case PrimitiveTypeKind.U32:
_builder.AppendLine($" %{outputName} =w extuh {input}");
return $"%{outputName}";
case PrimitiveTypeKind.I16:
case PrimitiveTypeKind.U16:
case PrimitiveTypeKind.I8:
case PrimitiveTypeKind.U8:
return input;
case PrimitiveTypeKind.F64:
{
var extName = GenVarName();
_builder.AppendLine($" %{extName} =l extuh {input}");
_builder.AppendLine($" %{outputName} =d ultof %{extName}");
return $"%{outputName}";
}
case PrimitiveTypeKind.F32:
{
var extName = GenVarName();
_builder.AppendLine($" %{extName} =w extuh {input}");
_builder.AppendLine($" %{outputName} =s uwtof %{extName}");
return $"%{outputName}";
}
case PrimitiveTypeKind.String:
{
var extName = GenVarName();
_builder.AppendLine($" %{extName} =w extuh {input}");
_builder.AppendLine($" %{outputName} =l call $nub_u32_to_string(w %{extName})");
return $"%{outputName}";
}
case PrimitiveTypeKind.Any:
{
var extName = GenVarName();
_builder.AppendLine($" %{extName} =l extuh {input}");
return $"%{extName}";
}
case PrimitiveTypeKind.Bool:
default:
throw new NotSupportedException($"Casting from {primitiveInputType.Kind} to {primitiveOutputType.Kind} is not supported");
}
case PrimitiveTypeKind.U8:
switch (primitiveOutputType.Kind)
{
case PrimitiveTypeKind.I64:
case PrimitiveTypeKind.U64:
_builder.AppendLine($" %{outputName} =l extub {input}");
return $"%{outputName}";
case PrimitiveTypeKind.I32:
case PrimitiveTypeKind.U32:
case PrimitiveTypeKind.I16:
case PrimitiveTypeKind.U16:
_builder.AppendLine($" %{outputName} =w extub {input}");
return $"%{outputName}";
case PrimitiveTypeKind.I8:
case PrimitiveTypeKind.U8:
return input;
case PrimitiveTypeKind.F64:
{
var extName = GenVarName();
_builder.AppendLine($" %{extName} =l extub {input}");
_builder.AppendLine($" %{outputName} =d ultof %{extName}");
return $"%{outputName}";
}
case PrimitiveTypeKind.F32:
{
var extName = GenVarName();
_builder.AppendLine($" %{extName} =w extub {input}");
_builder.AppendLine($" %{outputName} =s uwtof %{extName}");
return $"%{outputName}";
}
case PrimitiveTypeKind.String:
{
var extName = GenVarName();
_builder.AppendLine($" %{extName} =w extub {input}");
_builder.AppendLine($" %{outputName} =l call $nub_u32_to_string(w %{extName})");
return $"%{outputName}";
}
case PrimitiveTypeKind.Any:
{
var extName = GenVarName();
_builder.AppendLine($" %{extName} =l extub {input}");
return $"%{extName}";
}
case PrimitiveTypeKind.Bool:
default:
throw new NotSupportedException($"Casting from {primitiveInputType.Kind} to {primitiveOutputType.Kind} is not supported");
}
case PrimitiveTypeKind.F64:
switch (primitiveOutputType.Kind)
{
case PrimitiveTypeKind.F64:
case PrimitiveTypeKind.Any:
return input;
case PrimitiveTypeKind.F32:
_builder.AppendLine($" %{outputName} =s dtos {input}");
return $"%{outputName}";
case PrimitiveTypeKind.I64:
_builder.AppendLine($" %{outputName} =l dtosi {input}");
return $"%{outputName}";
case PrimitiveTypeKind.I32:
case PrimitiveTypeKind.I16:
case PrimitiveTypeKind.I8:
_builder.AppendLine($" %{outputName} =w dtosi {input}");
return $"%{outputName}";
case PrimitiveTypeKind.U64:
_builder.AppendLine($" %{outputName} =l dtoui {input}");
return $"%{outputName}";
case PrimitiveTypeKind.U32:
case PrimitiveTypeKind.U16:
case PrimitiveTypeKind.U8:
_builder.AppendLine($" %{outputName} =w dtoui {input}");
return $"%{outputName}";
case PrimitiveTypeKind.String:
_builder.AppendLine($" %{outputName} =l call $nub_f64_to_string(d {input})");
return $"%{outputName}";
case PrimitiveTypeKind.Bool:
default:
throw new NotSupportedException($"Casting from {primitiveInputType.Kind} to {primitiveOutputType.Kind} is not supported");
}
case PrimitiveTypeKind.F32:
switch (primitiveOutputType.Kind)
{
case PrimitiveTypeKind.F64:
_builder.AppendLine($" %{outputName} =d stod {input}");
return $"%{outputName}";
case PrimitiveTypeKind.F32:
return input;
case PrimitiveTypeKind.I64:
_builder.AppendLine($" %{outputName} =l stosi {input}");
return $"%{outputName}";
case PrimitiveTypeKind.I32:
case PrimitiveTypeKind.I16:
case PrimitiveTypeKind.I8:
_builder.AppendLine($" %{outputName} =w stosi {input}");
return $"%{outputName}";
case PrimitiveTypeKind.U64:
_builder.AppendLine($" %{outputName} =l stoui {input}");
return $"%{outputName}";
case PrimitiveTypeKind.U32:
case PrimitiveTypeKind.U16:
case PrimitiveTypeKind.U8:
_builder.AppendLine($" %{outputName} =w stoui {input}");
return $"%{outputName}";
case PrimitiveTypeKind.String:
_builder.AppendLine($" %{outputName} =l call $nub_f32_to_string(s {input})");
return $"%{outputName}";
case PrimitiveTypeKind.Any:
throw new NotImplementedException();
case PrimitiveTypeKind.Bool:
default:
throw new NotSupportedException($"Casting from {primitiveInputType.Kind} to {primitiveOutputType.Kind} is not supported");
}
case PrimitiveTypeKind.Bool:
switch (primitiveOutputType.Kind)
{
case PrimitiveTypeKind.Bool:
return input;
case PrimitiveTypeKind.Any:
_builder.AppendLine($" %{outputName} =l extsw {input}");
return $"%{outputName}";
case PrimitiveTypeKind.String:
_builder.AppendLine($" %{outputName} =l call $nub_bool_to_string(s {input})");
return $"%{outputName}";
case PrimitiveTypeKind.I64:
case PrimitiveTypeKind.I32:
case PrimitiveTypeKind.I16:
case PrimitiveTypeKind.I8:
case PrimitiveTypeKind.U64:
case PrimitiveTypeKind.U32:
case PrimitiveTypeKind.U16:
case PrimitiveTypeKind.U8:
case PrimitiveTypeKind.F64:
case PrimitiveTypeKind.F32:
default:
throw new NotSupportedException($"Casting from {primitiveInputType.Kind} to {primitiveOutputType.Kind} is not supported");
}
case PrimitiveTypeKind.String:
switch (primitiveOutputType.Kind)
{
case PrimitiveTypeKind.String:
case PrimitiveTypeKind.Any:
return input;
case PrimitiveTypeKind.I64:
case PrimitiveTypeKind.I32:
case PrimitiveTypeKind.I16:
case PrimitiveTypeKind.I8:
case PrimitiveTypeKind.U64:
case PrimitiveTypeKind.U32:
case PrimitiveTypeKind.U16:
case PrimitiveTypeKind.U8:
case PrimitiveTypeKind.F64:
case PrimitiveTypeKind.F32:
case PrimitiveTypeKind.Bool:
default:
throw new NotSupportedException($"Casting from {primitiveInputType.Kind} to {primitiveOutputType.Kind} is not supported");
}
case PrimitiveTypeKind.Any:
return input;
default:
throw new NotSupportedException($"Casting from {primitiveInputType.Kind} to {primitiveOutputType.Kind} is not supported");
}
}
private string GenerateIdentifier(IdentifierNode identifier)
{
var variable = _variables[identifier.Identifier];
var outputName = GenVarName();
_builder.AppendLine($" %{outputName} ={SQT(identifier.Type)} load{SQT(identifier.Type)} {variable.Pointer}");
return $"%{outputName}";
}
private string GenerateLiteral(LiteralNode literal)
{
if (literal.LiteralType.Equals(NubPrimitiveType.String))
{
_strings.Add(literal.Literal);
return $"$str{_strings.Count}";
}
if (literal.LiteralType.Equals(NubPrimitiveType.I64))
{
return literal.Literal;
}
if (literal.LiteralType.Equals(NubPrimitiveType.F64))
{
var value = double.Parse(literal.Literal, CultureInfo.InvariantCulture);
var bits = BitConverter.DoubleToInt64Bits(value);
return bits.ToString();
}
if (literal.LiteralType.Equals(NubPrimitiveType.Bool))
{
return bool.Parse(literal.Literal) ? "1" : "0";
}
throw new NotSupportedException($"Literal {literal.LiteralType} is not supported");
}
private string GenerateStructInitializer(StructInitializerNode structInitializer)
{
var structDefinition = LookupStructDefinition(structInitializer.StructType.Namespace, structInitializer.StructType.Name);
if (structDefinition == null)
{
throw new Exception($"Struct {structInitializer.StructType.Name} is not defined");
}
var structVar = GenVarName();
var size = structDefinition.Fields.Sum(x => QbeTypeSize(x.Type));
_builder.AppendLine($" %{structVar} =l alloc8 {size}");
for (var i = 0; i < structDefinition.Fields.Count; i++)
{
var field = structDefinition.Fields[i];
if (structInitializer.Initializers.TryGetValue(field.Name, out var fieldValue))
{
var var = GenerateExpression(fieldValue);
var offsetName = GenVarName();
_builder.AppendLine($" %{offsetName} =l add %{structVar}, {i * QbeTypeSize(field.Type)}");
_builder.AppendLine($" store{SQT(field.Type)} {var}, %{offsetName}");
}
else if (field.Value.HasValue)
{
var var = GenerateExpression(field.Value.Value);
var offsetName = GenVarName();
_builder.AppendLine($" %{offsetName} =l add %{structVar}, {i * QbeTypeSize(field.Type)}");
_builder.AppendLine($" store{SQT(field.Type)} {var}, %{offsetName}");
}
else
{
throw new Exception($"Field {field.Name} on struct {structInitializer.StructType.Name} is not initialized");
}
}
return $"%{structVar}";
}
private string GenerateUnaryExpression(UnaryExpressionNode unaryExpression)
{
var operand = GenerateExpression(unaryExpression.Operand);
var outputName = GenVarName();
switch (unaryExpression.Operator)
{
case UnaryExpressionOperator.Negate:
{
switch (unaryExpression.Operand.Type)
{
case NubPrimitiveType { Kind: PrimitiveTypeKind.I64 }:
_builder.AppendLine($" %{outputName} =l neg {operand}");
return $"%{outputName}";
case NubPrimitiveType { Kind: PrimitiveTypeKind.I32 or PrimitiveTypeKind.I16 or PrimitiveTypeKind.I8 }:
_builder.AppendLine($" %{outputName} =w neg {operand}");
return $"%{outputName}";
case NubPrimitiveType { Kind: PrimitiveTypeKind.F64 }:
_builder.AppendLine($" %{outputName} =d neg {operand}");
return $"%{outputName}";
case NubPrimitiveType { Kind: PrimitiveTypeKind.F32 }:
_builder.AppendLine($" %{outputName} =s neg {operand}");
return $"%{outputName}";
}
break;
}
case UnaryExpressionOperator.Invert:
{
switch (unaryExpression.Operand.Type)
{
case NubPrimitiveType { Kind: PrimitiveTypeKind.Bool }:
_builder.AppendLine($" %{outputName} =w xor {operand}, 1");
return $"%{outputName}";
}
break;
}
default:
{
throw new ArgumentOutOfRangeException();
}
}
throw new NotSupportedException($"Unary operator {unaryExpression.Operator} for type {unaryExpression.Operand.Type} not supported");
}
private string GenerateMemberAccess(MemberAccessNode memberAccess)
{
var expression = GenerateExpression(memberAccess.Expression);
switch (memberAccess.Expression.Type)
{
case NubArrayType:
{
if (memberAccess.Member == "count")
{
var outputName = GenVarName();
_builder.AppendLine($" %{outputName} =l loadl {expression}");
return $"%{outputName}";
}
break;
}
case NubStructType structType:
{
var structDefinition = LookupStructDefinition(structType.Namespace, structType.Namespace);
if (structDefinition == null)
{
throw new Exception($"Struct {structType.Name} is not defined");
}
var fieldIndex = -1;
for (var i = 0; i < structDefinition.Fields.Count; i++)
{
if (structDefinition.Fields[i].Name == memberAccess.Member)
{
fieldIndex = i;
break;
}
}
if (fieldIndex == -1)
{
throw new Exception($"Field {memberAccess.Member} is not defined in struct {structType.Name}");
}
var offsetName = GenVarName();
_builder.AppendLine($" %{offsetName} =l add {expression}, {fieldIndex * QbeTypeSize(memberAccess.Type)}");
var outputName = GenVarName();
_builder.AppendLine($" %{outputName} ={SQT(memberAccess.Type)} load{SQT(memberAccess.Type)} %{offsetName}");
return $"%{outputName}";
}
}
throw new ArgumentOutOfRangeException(nameof(memberAccess.Expression.Type));
}
private string GenerateExpressionFuncCall(FuncCallExpressionNode funcCall)
{
var outputName = GenVarName();
var call = GenerateFuncCall(funcCall.FuncCall);
_builder.AppendLine($" %{outputName} ={SQT(funcCall.Type)} {call}");
return $"%{outputName}";
}
private string GenVarName()
{
return $"v{++_variableIndex}";
}
private string GenFuncName()
{
return $"f{++_funcIndex}";
}
private string GenLabelName()
{
return $"l{++_labelIndex}";
}
private IFuncSignature? LookupFuncSignature(string @namespace, string name, List<NubType> parameters)
{
return _sourceFiles
.Where(f => f.Namespace == @namespace)
.SelectMany(f => f.Definitions)
.OfType<IFuncSignature>()
.FirstOrDefault(f => f.SignatureMatches(name, parameters));
}
private StructDefinitionNode? LookupStructDefinition(string @namespace, string name)
{
return _sourceFiles
.Where(f => f.Namespace == @namespace)
.SelectMany(f => f.Definitions)
.OfType<StructDefinitionNode>()
.FirstOrDefault(d => d.Name == name);
}
private class Variable
{
public required string Pointer { get; init; }
public required NubType Type { get; init; }
}
}