diff --git a/example/main.nub b/example/main.nub index 38296d7..61b8abc 100644 --- a/example/main.nub +++ b/example/main.nub @@ -6,12 +6,10 @@ struct Human { } export func main(args: []^string) { - let human = [1]Human - - human[0] = alloc Human { + let me = alloc Human { name = "oliver" - age = 123 + age = 32 } - c::printf("%s\n", human[1].name) + c::printf("%s is %d years old\n", me.name, me.age) } \ No newline at end of file diff --git a/src/lang/Nub.Lang/Frontend/Generation/Generator.cs b/src/lang/Nub.Lang/Frontend/Generation/Generator.cs index 474e559..a70e9c0 100644 --- a/src/lang/Nub.Lang/Frontend/Generation/Generator.cs +++ b/src/lang/Nub.Lang/Frontend/Generation/Generator.cs @@ -86,143 +86,175 @@ public class Generator return _builder.ToString(); } - private static string SQT(NubType type) + private enum TypeContext { - 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(); - } - } + Struct, + FuncDef, + FuncCall, } - private static string EQT(NubType type) + private static string QBEType(NubType type, TypeContext context) { - switch (type) + return context switch { - case NubPrimitiveType primitiveType: + TypeContext.Struct => type switch { - switch (primitiveType.Kind) + NubArrayType => "l", // TODO: Arrays in structs are pointers for now + NubPointerType => "l", + NubPrimitiveType primitiveType => primitiveType.Kind switch { - 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: + PrimitiveTypeKind.I64 => "l", + PrimitiveTypeKind.I32 => "w", + PrimitiveTypeKind.I16 => "h", + PrimitiveTypeKind.I8 => "b", + PrimitiveTypeKind.U64 => "l", + PrimitiveTypeKind.U32 => "w", + PrimitiveTypeKind.U16 => "h", + PrimitiveTypeKind.U8 => "b", + PrimitiveTypeKind.F64 => "d", + PrimitiveTypeKind.F32 => "s", + PrimitiveTypeKind.Bool => "w", + PrimitiveTypeKind.String => "l", // TODO: Strings in structs are pointers for now + PrimitiveTypeKind.Any => throw new NotSupportedException("any type cannot be used in structs"), + _ => throw new ArgumentOutOfRangeException() + }, + NubStructType => throw new NotImplementedException(), + _ => throw new ArgumentOutOfRangeException(nameof(type)) + }, + TypeContext.FuncDef => type switch { - return ":" + nubCustomType.Name; - } - case NubPointerType: - case NubArrayType: + NubArrayType => "l", + NubPointerType => "l", + NubPrimitiveType primitiveType => primitiveType.Kind switch + { + PrimitiveTypeKind.I64 => "l", + PrimitiveTypeKind.I32 => "w", + PrimitiveTypeKind.I16 => "sh", + PrimitiveTypeKind.I8 => "sb", + PrimitiveTypeKind.U64 => "l", + PrimitiveTypeKind.U32 => "w", + PrimitiveTypeKind.U16 => "uh", + PrimitiveTypeKind.U8 => "ub", + PrimitiveTypeKind.F64 => "d", + PrimitiveTypeKind.F32 => "s", + PrimitiveTypeKind.Bool => "w", + PrimitiveTypeKind.String => "l", + PrimitiveTypeKind.Any => throw new NotSupportedException("any type cannot be used in function definitions"), + _ => throw new ArgumentOutOfRangeException() + }, + NubStructType structType => $":{structType.Namespace}_{structType.Name}", + _ => throw new ArgumentOutOfRangeException(nameof(type)) + }, + TypeContext.FuncCall => type switch { - return "l"; - } - default: - { - throw new NotImplementedException(); - } - } + NubArrayType => "l", + NubPointerType => "l", + NubPrimitiveType pointerType => pointerType.Kind switch + { + PrimitiveTypeKind.I64 => "l", + PrimitiveTypeKind.I32 => "w", + PrimitiveTypeKind.I16 => "sh", + PrimitiveTypeKind.I8 => "sb", + PrimitiveTypeKind.U64 => "l", + PrimitiveTypeKind.U32 => "w", + PrimitiveTypeKind.U16 => "uh", + PrimitiveTypeKind.U8 => "ub", + PrimitiveTypeKind.F64 => "d", + PrimitiveTypeKind.F32 => "s", + PrimitiveTypeKind.Bool => "w", + PrimitiveTypeKind.String => "l", + PrimitiveTypeKind.Any => "l", + _ => throw new ArgumentOutOfRangeException() + }, + NubStructType structType => $":{structType.Namespace}_{structType.Name}", + _ => throw new ArgumentOutOfRangeException(nameof(type)) + }, + _ => throw new ArgumentOutOfRangeException(nameof(context), context, null) + }; } - private static string FQT(NubType type) + private static string QBEStore(NubType type) { - switch (type) + return $"store{type switch { - case NubPrimitiveType primitiveType: + NubArrayType => "l", + NubPointerType => "l", + NubPrimitiveType primitiveType => primitiveType.Kind switch { - 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: + PrimitiveTypeKind.I64 => "l", + PrimitiveTypeKind.I32 => "w", + PrimitiveTypeKind.I16 => "h", + PrimitiveTypeKind.I8 => "b", + PrimitiveTypeKind.U64 => "l", + PrimitiveTypeKind.U32 => "w", + PrimitiveTypeKind.U16 => "h", + PrimitiveTypeKind.U8 => "b", + PrimitiveTypeKind.F64 => "d", + PrimitiveTypeKind.F32 => "s", + PrimitiveTypeKind.Bool => "w", + PrimitiveTypeKind.String => "l", + PrimitiveTypeKind.Any => throw new NotSupportedException("any type cannot be used in store instructions"), + _ => throw new ArgumentOutOfRangeException() + }, + NubStructType => "l", + _ => throw new ArgumentOutOfRangeException(nameof(type)) + }}"; + } + + private static string QBELoad(NubType type) + { + return $"load{type switch + { + NubArrayType => "l", + NubPointerType => "l", + NubPrimitiveType primitiveType => primitiveType.Kind switch { - return ":" + nubCustomType.Name; - } - case NubPointerType: - case NubArrayType: + PrimitiveTypeKind.I64 => "l", + PrimitiveTypeKind.I32 => "w", + PrimitiveTypeKind.I16 => "sh", + PrimitiveTypeKind.I8 => "sb", + PrimitiveTypeKind.U64 => "l", + PrimitiveTypeKind.U32 => "w", + PrimitiveTypeKind.U16 => "uh", + PrimitiveTypeKind.U8 => "ub", + PrimitiveTypeKind.F64 => "d", + PrimitiveTypeKind.F32 => "s", + PrimitiveTypeKind.Bool => "w", + PrimitiveTypeKind.String => "l", + PrimitiveTypeKind.Any => throw new NotSupportedException("any type cannot be used in load instructions"), + _ => throw new ArgumentOutOfRangeException() + }, + NubStructType => "l", + _ => throw new ArgumentOutOfRangeException(nameof(type)) + }}"; + } + + private static string QBEAssign(NubType type) + { + return $"={type switch + { + NubArrayType => "l", + NubPointerType => "l", + NubPrimitiveType primitiveType => primitiveType.Kind switch { - return "l"; - } - default: - { - throw new NotImplementedException(); - } - } + PrimitiveTypeKind.I64 => "l", + PrimitiveTypeKind.I32 => "w", + PrimitiveTypeKind.I16 => "w", + PrimitiveTypeKind.I8 => "w", + PrimitiveTypeKind.U64 => "l", + PrimitiveTypeKind.U32 => "w", + PrimitiveTypeKind.U16 => "w", + PrimitiveTypeKind.U8 => "w", + PrimitiveTypeKind.F64 => "d", + PrimitiveTypeKind.F32 => "s", + PrimitiveTypeKind.Bool => "w", + PrimitiveTypeKind.String => "l", + PrimitiveTypeKind.Any => throw new NotSupportedException("any type cannot be used in variables"), + _ => throw new ArgumentOutOfRangeException() + }, + NubStructType => "l", + _ => throw new ArgumentOutOfRangeException(nameof(type)) + }}"; } private int QbeTypeSize(NubType type) @@ -297,7 +329,7 @@ public class Generator _builder.Append("function "); if (node.ReturnType.HasValue) { - _builder.Append($"{FQT(node.ReturnType.Value)} "); + _builder.Append($"{QBEType(node.ReturnType.Value, TypeContext.FuncDef)} "); } else if (!node.ReturnType.HasValue && node.Name == "main") { @@ -307,7 +339,7 @@ public class Generator _builder.Append('$'); _builder.Append(_funcNames[node]); - var parameterStrings = node.Parameters.Select(parameter => parameter.Variadic ? "..." : $"{FQT(parameter.Type)} %{parameter.Name}"); + var parameterStrings = node.Parameters.Select(parameter => parameter.Variadic ? "..." : $"{QBEType(parameter.Type, TypeContext.FuncDef)} %{parameter.Name}"); _builder.AppendLine($"({string.Join(", ", parameterStrings)}) {{"); _builder.AppendLine("@start"); @@ -316,7 +348,7 @@ public class Generator { var parameterName = parameter.Name; - switch (FQT(parameter.Type)) + switch (QBEType(parameter.Type, TypeContext.FuncDef)) { case "sb": parameterName = GenVarName(); @@ -337,7 +369,7 @@ public class Generator } var pointerName = GenVarName(); - _builder.AppendLine($" %{pointerName} ={SQT(parameter.Type)} alloc8 {QbeTypeSize(parameter.Type)}"); + _builder.AppendLine($" %{pointerName} {QBEAssign(parameter.Type)} alloc8 {QbeTypeSize(parameter.Type)}"); _builder.AppendLine($" storel %{parameterName}, %{pointerName}"); _variables[parameter.Name] = new Variable @@ -347,6 +379,8 @@ public class Generator }; } + _builder.AppendLine(); + GenerateBlock(node.Body); if (node.Body.Statements.LastOrDefault() is not ReturnNode) @@ -370,7 +404,7 @@ public class Generator private void GenerateStructDefinition(StructDefinitionNode structDefinition) { - var fields = structDefinition.Fields.Select(f => EQT(f.Type)); + var fields = structDefinition.Fields.Select(f => QBEType(f.Type, TypeContext.Struct)); _builder.AppendLine($"type :{structDefinition.Name} = {{ {string.Join(", ", fields)} }}"); } @@ -387,6 +421,9 @@ public class Generator case ContinueNode: GenerateContinue(); break; + case DereferenceAssignmentNode dereferenceAssignment: + GenerateDereferenceAssignment(dereferenceAssignment); + break; case IfNode ifStatement: GenerateIf(ifStatement); break; @@ -439,7 +476,7 @@ public class Generator } else { - _builder.AppendLine($" store{SQT(arrayType.BaseType)} {value}, %{offsetName}"); + _builder.AppendLine($" {QBEStore(arrayType.BaseType)} {value}, %{offsetName}"); } } @@ -465,6 +502,21 @@ public class Generator _codeIsReachable = false; } + private void GenerateDereferenceAssignment(DereferenceAssignmentNode dereferenceAssignment) + { + var location = GenerateExpression(dereferenceAssignment.Dereference.Expression); + var value = GenerateExpression(dereferenceAssignment.Value); + + if (IsLargeType(dereferenceAssignment.Value.Type)) + { + _builder.AppendLine($" blit {value}, {location}, {QbeTypeSize(dereferenceAssignment.Value.Type)}"); + } + else + { + _builder.AppendLine($" {QBEStore(dereferenceAssignment.Value.Type)} {value}, {location}"); + } + } + private void GenerateIf(IfNode ifStatement) { var trueLabel = GenLabelName(); @@ -532,16 +584,31 @@ public class Generator var type = variableDeclaration.ExplicitType.Value ?? variableDeclaration.Value.Value?.Type!; - _builder.AppendLine($" %{pointerName} ={SQT(type)} alloc8 {QbeTypeSize(type)}"); + _builder.AppendLine($" %{pointerName} {QBEAssign(type)} alloc8 {QbeTypeSize(type)}"); if (variableDeclaration.Value.HasValue) { var result = GenerateExpression(variableDeclaration.Value.Value); - _builder.AppendLine($" storel {result}, %{pointerName}"); + + if (IsLargeType(type)) + { + _builder.AppendLine($" blit {result}, %{pointerName}, {QbeTypeSize(type)}"); + } + else + { + _builder.AppendLine($" {QBEStore(type)} {result}, %{pointerName}"); + } } else { - _builder.AppendLine($" storel 0, %{pointerName}"); + if (IsLargeType(type)) + { + _builder.AppendLine($" blit 0, %{pointerName}, {QbeTypeSize(type)}"); + } + else + { + _builder.AppendLine($" {QBEStore(type)} 0, %{pointerName}"); + } } _variables[variableDeclaration.Name] = new Variable @@ -615,7 +682,7 @@ public class Generator else { var outputName = GenVarName(); - _builder.AppendLine($" %{outputName} ={SQT(arrayType)} load{SQT(arrayType)} %{resultPointerName}"); + _builder.AppendLine($" %{outputName} {QBEAssign(arrayType)} {QBELoad(arrayType)} %{resultPointerName}"); return $"%{outputName}"; } } @@ -666,7 +733,7 @@ public class Generator { var result = GenerateExpression(dereference.Expression); var outputName = GenVarName(); - _builder.AppendLine($" %{outputName} ={SQT(dereference.Type)} load{SQT(dereference.Type)} {result}"); + _builder.AppendLine($" %{outputName} {QBEAssign(dereference.Type)} {QBELoad(dereference.Type)} {result}"); return $"%{outputName}"; } @@ -1379,7 +1446,7 @@ public class Generator else { var outputName = GenVarName(); - _builder.AppendLine($" %{outputName} ={SQT(identifier.Type)} load{SQT(identifier.Type)} {variable.Pointer}"); + _builder.AppendLine($" %{outputName} {QBEAssign(identifier.Type)} {QBELoad(identifier.Type)} {variable.Pointer}"); return $"%{outputName}"; } } @@ -1436,7 +1503,7 @@ public class Generator } else { - _builder.AppendLine($" store{SQT(field.Type)} {var}, %{offsetName}"); + _builder.AppendLine($" {QBEStore(field.Type)} {var}, %{offsetName}"); } } else if (field.Value.HasValue) @@ -1451,7 +1518,7 @@ public class Generator } else { - _builder.AppendLine($" store{SQT(field.Type)} {var}, %{offsetName}"); + _builder.AppendLine($" {QBEStore(field.Type)} {var}, %{offsetName}"); } } else @@ -1542,7 +1609,7 @@ public class Generator else { var outputName = GenVarName(); - _builder.AppendLine($" %{outputName} ={SQT(memberAccess.Type)} load{SQT(memberAccess.Type)} %{offsetName}"); + _builder.AppendLine($" %{outputName} {QBEAssign(memberAccess.Type)} {QBELoad(memberAccess.Type)} %{offsetName}"); return $"%{outputName}"; } @@ -1556,8 +1623,6 @@ public class Generator private string GenerateFuncCall(FuncCallNode funcCall) { - var outputName = GenVarName(); - var funcDefinition = LookupFuncSignature(funcCall.Namespace, funcCall.Name, funcCall.Parameters.Select(p => p.Type).ToList()); if (funcDefinition == null) { @@ -1573,33 +1638,26 @@ public class Generator 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); + var qbeParameterType = QBEType(parameter.Type, TypeContext.FuncCall); parameterStrings.Add($"{qbeParameterType} {result}"); } var funcName = _funcNames[funcDefinition]; - var call = $"call ${funcName}({string.Join(", ", parameterStrings)})"; - - _builder.AppendLine($" %{outputName} ={SQT(funcCall.Type)} {call}"); - return $"%{outputName}"; + if (funcDefinition.ReturnType.HasValue) + { + var outputName = GenVarName(); + _builder.AppendLine($" %{outputName} {QBEAssign(funcCall.Type)} call ${funcName}({string.Join(", ", parameterStrings)})"); + return $"%{outputName}"; + } + else + { + _builder.AppendLine($" call ${funcName}({string.Join(", ", parameterStrings)})"); + return "this should never show up!"; + } } private string GenVarName() diff --git a/src/lang/Nub.Lang/Frontend/Parsing/Parser.cs b/src/lang/Nub.Lang/Frontend/Parsing/Parser.cs index a3698b0..8573c59 100644 --- a/src/lang/Nub.Lang/Frontend/Parsing/Parser.cs +++ b/src/lang/Nub.Lang/Frontend/Parsing/Parser.cs @@ -223,24 +223,32 @@ public class Parser { case Symbol.Assign: { - Next(); switch (expr) { case MemberAccessNode memberAccess: { + Next(); var value = ParseExpression(); return new MemberAssignmentNode(GetTokensForNode(startIndex), memberAccess, value); } case ArrayIndexAccessNode arrayIndexAccess: { + Next(); var value = ParseExpression(); return new ArrayIndexAssignmentNode(GetTokensForNode(startIndex), arrayIndexAccess, value); } case IdentifierNode identifier: { + Next(); var value = ParseExpression(); return new VariableAssignmentNode(GetTokensForNode(startIndex), identifier, value); } + case DereferenceNode dereference: + { + Next(); + var value = ParseExpression(); + return new DereferenceAssignmentNode(GetTokensForNode(startIndex), dereference, value); + } } break; diff --git a/src/lang/Nub.Lang/Frontend/Parsing/Statements/DereferenceAssignmentNode.cs b/src/lang/Nub.Lang/Frontend/Parsing/Statements/DereferenceAssignmentNode.cs new file mode 100644 index 0000000..60b1632 --- /dev/null +++ b/src/lang/Nub.Lang/Frontend/Parsing/Statements/DereferenceAssignmentNode.cs @@ -0,0 +1,10 @@ +using Nub.Lang.Frontend.Lexing; +using Nub.Lang.Frontend.Parsing.Expressions; + +namespace Nub.Lang.Frontend.Parsing.Statements; + +public class DereferenceAssignmentNode(IReadOnlyList tokens, DereferenceNode dereference, ExpressionNode value) : StatementNode(tokens) +{ + public DereferenceNode Dereference { get; } = dereference; + public ExpressionNode Value { get; } = value; +} \ No newline at end of file diff --git a/src/lang/Nub.Lang/Frontend/Typing/TypeChecker.cs b/src/lang/Nub.Lang/Frontend/Typing/TypeChecker.cs index 9a3753e..1f919d8 100644 --- a/src/lang/Nub.Lang/Frontend/Typing/TypeChecker.cs +++ b/src/lang/Nub.Lang/Frontend/Typing/TypeChecker.cs @@ -138,6 +138,9 @@ public class TypeChecker case BreakNode: case ContinueNode: break; + case DereferenceAssignmentNode dereferenceAssignment: + TypeCheckDereferenceAssignment(dereferenceAssignment); + break; default: ReportError($"Unsupported statement type: {statement.GetType().Name}", statement); break; @@ -347,6 +350,19 @@ public class TypeChecker } } + private void TypeCheckDereferenceAssignment(DereferenceAssignmentNode dereferenceAssignment) + { + var dereferenceType = TypeCheckExpression(dereferenceAssignment.Dereference); + if (dereferenceType == null) return; + var valueType = TypeCheckExpression(dereferenceAssignment.Value); + if (valueType == null) return; + + if (!NubType.IsCompatibleWith(dereferenceType, valueType)) + { + ReportError($"'{valueType}' is not assignable to type '{dereferenceType}'", dereferenceAssignment); + } + } + private NubType? TypeCheckExpression(ExpressionNode expression) { var resultType = expression switch