From dce5a2b56672b95d4ae41d0b4a27bbfcb129b47d Mon Sep 17 00:00:00 2001 From: nub31 Date: Sun, 8 Jun 2025 17:26:50 +0200 Subject: [PATCH] Fix type checking for anonymous functions --- build.sh | 8 ++- example/main.nub | 33 +++++++-- run.sh | 1 - .../Frontend/Generation/QBEGenerator.cs | 67 ++++++++++++++----- .../Parsing/Expressions/AnonymousFuncNode.cs | 13 ++++ src/lang/Nub.Lang/Frontend/Parsing/Parser.cs | 25 +++++++ .../Nub.Lang/Frontend/Typing/TypeChecker.cs | 24 +++++-- 7 files changed, 140 insertions(+), 31 deletions(-) create mode 100644 src/lang/Nub.Lang/Frontend/Parsing/Expressions/AnonymousFuncNode.cs diff --git a/build.sh b/build.sh index 0fa2191..7008db0 100755 --- a/build.sh +++ b/build.sh @@ -1,16 +1,18 @@ #!/bin/bash set -e +./clean.sh + mkdir -p out dotnet build src/lang/Nub.Lang.CLI -nub example > out/out.qbe +nub example > out/out.ssa nasm -g -felf64 src/runtime/runtime.asm -o out/runtime.o nasm -g -felf64 src/runtime/core/syscall.asm -o out/syscall.o -qbe out/out.qbe > out/out.s +qbe out/out.ssa > out/out.s gcc -c -g out/out.s -o out/out.o -gcc -nostartfiles -o out/program out/runtime.o out/syscall.o out/out.o \ No newline at end of file +gcc -nostartfiles -o out/program out/runtime.o out/syscall.o out/out.o -no-pie \ No newline at end of file diff --git a/example/main.nub b/example/main.nub index 49008ae..a2eac42 100644 --- a/example/main.nub +++ b/example/main.nub @@ -2,23 +2,42 @@ namespace main struct Human { age: u64 - print_age: func() = () => { - + print_age: func() = func() { + c::puts("pwp") } } export func main(args: []^string): i64 { + let x = 2 + + let uwu = func() { + c::puts("uwu") + } + + uwu() + + func() { + c::puts("owo") + }() + let me = alloc Human { age = 23 } me.print_age() - print_age() + if true { + // do something + } + + c::puts("test") + + let i = 1 + while i <= 10 { + c::puts("test") + i = i + 1 + } + return 0 -} - -func print_age() { - c::puts("TEST") } \ No newline at end of file diff --git a/run.sh b/run.sh index 2572e63..000d377 100755 --- a/run.sh +++ b/run.sh @@ -1,5 +1,4 @@ #!/bin/bash set -e -./clean.sh ./build.sh bash -c './out/program; echo "Process exited with status code $?"' diff --git a/src/lang/Nub.Lang/Frontend/Generation/QBEGenerator.cs b/src/lang/Nub.Lang/Frontend/Generation/QBEGenerator.cs index 745f443..e863f6f 100644 --- a/src/lang/Nub.Lang/Frontend/Generation/QBEGenerator.cs +++ b/src/lang/Nub.Lang/Frontend/Generation/QBEGenerator.cs @@ -24,6 +24,8 @@ public class QBEGenerator private int _labelIndex; private bool _codeIsReachable = true; private Dictionary _funcNames = []; + private Dictionary _anonymousFunctions = []; + private int _anonymousFuncIndex; public string Generate(List sourceFiles) { @@ -34,8 +36,10 @@ public class QBEGenerator _funcNames = []; _breakLabels = []; _continueLabels = []; + _anonymousFunctions = []; _variableIndex = 0; _labelIndex = 0; + _anonymousFuncIndex = 0; _codeIsReachable = true; foreach (var structDef in _sourceFiles.SelectMany(f => f.Definitions).OfType()) @@ -69,7 +73,13 @@ public class QBEGenerator foreach (var funcDef in _sourceFiles.SelectMany(f => f.Definitions).OfType()) { - GenerateFuncDefinition(funcDef); + GenerateFuncDefinition(_funcNames[funcDef], funcDef.Parameters, funcDef.ReturnType, funcDef.Body, funcDef.Exported); + _builder.AppendLine(); + } + + foreach (var (func, name) in _anonymousFunctions) + { + GenerateFuncDefinition(name, func.Parameters, func.ReturnType, func.Body, false); _builder.AppendLine(); } @@ -304,19 +314,19 @@ public class QBEGenerator }; } - private void GenerateFuncDefinition(LocalFuncDefinitionNode node) + private void GenerateFuncDefinition(string name, List parameters, NubType returnType, BlockNode body, bool exported) { _variables.Clear(); - if (node.Exported) + if (exported) { _builder.Append("export "); } _builder.Append("function "); - if (node.ReturnType is not NubVoidType) + if (returnType is not NubVoidType) { - _builder.Append(node.ReturnType switch + _builder.Append(returnType switch { NubArrayType => "l", NubPointerType => "l", @@ -338,14 +348,14 @@ public class QBEGenerator NubStructType structType => $":{structType.Namespace}_{structType.Name}", NubFixedArrayType => "l", NubFuncType => "l", - _ => throw new NotSupportedException($"'{node.ReturnType}' type cannot be used as a function return type") + _ => throw new NotSupportedException($"'{returnType}' type cannot be used as a function return type") }); _builder.Append(' '); } - _builder.Append(_funcNames[node]); + _builder.Append(name); - var parameterStrings = node.Parameters.Select(parameter => $"{parameter.Type switch + var parameterStrings = parameters.Select(parameter => $"{parameter.Type switch { NubArrayType => "l", NubPointerType => "l", @@ -373,7 +383,7 @@ public class QBEGenerator _builder.AppendLine($"({string.Join(", ", parameterStrings)}) {{"); _builder.AppendLine("@start"); - foreach (var parameter in node.Parameters) + foreach (var parameter in parameters) { var parameterName = "%" + parameter.Name; @@ -403,11 +413,11 @@ public class QBEGenerator _variables[parameter.Name] = parameterName; } - GenerateBlock(node.Body); + GenerateBlock(body); - if (node.Body.Statements.LastOrDefault() is not ReturnNode) + if (body.Statements.LastOrDefault() is not ReturnNode) { - if (node.ReturnType is NubVoidType) + if (returnType is NubVoidType) { _builder.AppendLine(" ret"); } @@ -673,6 +683,7 @@ public class QBEGenerator return expression switch { AddressOfNode addressOf => GenerateAddressOf(addressOf), + AnonymousFuncNode anonymousFunc => GenerateAnonymousFunc(anonymousFunc), ArrayIndexAccessNode arrayIndex => GenerateArrayAccessIndex(arrayIndex), ArrayInitializerNode arrayInitializer => GenerateArrayInitializer(arrayInitializer), BinaryExpressionNode binaryExpression => GenerateBinaryExpression(binaryExpression), @@ -688,6 +699,16 @@ public class QBEGenerator }; } + private string GenerateAnonymousFunc(AnonymousFuncNode anonymousFunc) + { + var name = $"$anon_func{++_anonymousFuncIndex}"; + _anonymousFunctions[anonymousFunc] = name; + var pointer = GenVarName(); + _builder.AppendLine($" {pointer} =l alloc8 8"); + _builder.AppendLine($" storel {name}, {pointer}"); + return pointer; + } + private string GenerateArrayIndexPointer(ArrayIndexAccessNode arrayIndexAccess) { var array = GenerateExpression(arrayIndexAccess.Array); @@ -1037,11 +1058,24 @@ public class QBEGenerator { if (_variables.TryGetValue(identifier.Name, out var value)) { - return value; + if (IsLargeType(identifier.Type)) + { + return value; + } + else + { + var result = GenVarName(); + _builder.AppendLine($" {result} {QBEAssign(identifier.Type)} {QBELoad(identifier.Type)} {value}"); + return result; + } } else { - return _funcNames[LookupFuncSignature(identifier.Namespace, identifier.Name)]; + var funcName = _funcNames[LookupFuncSignature(identifier.Namespace, identifier.Name)]; + var pointer = GenVarName(); + _builder.AppendLine($" {pointer} =l alloc8 8"); + _builder.AppendLine($" storel {funcName}, {pointer}"); + return pointer; } } @@ -1270,7 +1304,10 @@ public class QBEGenerator parameterStrings.Add($"{qbeParameterType} {result}"); } - var funcPointer = GenerateExpression(funcCall.Expression); + // var funcPointer = GenerateExpression(funcCall.Expression); + var funcPointerPointer = GenerateExpression(funcCall.Expression); + var funcPointer = GenVarName(); + _builder.AppendLine($" {funcPointer} =l loadl {funcPointerPointer}"); if (funcType.ReturnType is not NubVoidType) { diff --git a/src/lang/Nub.Lang/Frontend/Parsing/Expressions/AnonymousFuncNode.cs b/src/lang/Nub.Lang/Frontend/Parsing/Expressions/AnonymousFuncNode.cs new file mode 100644 index 0000000..2162e0a --- /dev/null +++ b/src/lang/Nub.Lang/Frontend/Parsing/Expressions/AnonymousFuncNode.cs @@ -0,0 +1,13 @@ +using Nub.Lang.Frontend.Lexing; +using Nub.Lang.Frontend.Parsing.Definitions; +using Nub.Lang.Frontend.Parsing.Statements; +using Nub.Lang.Frontend.Typing; + +namespace Nub.Lang.Frontend.Parsing.Expressions; + +public class AnonymousFuncNode(IReadOnlyList tokens, List parameters, BlockNode body, NubType returnType) : ExpressionNode(tokens) +{ + public List Parameters { get; } = parameters; + public BlockNode Body { get; } = body; + public NubType ReturnType { get; } = returnType; +} \ No newline at end of file diff --git a/src/lang/Nub.Lang/Frontend/Parsing/Parser.cs b/src/lang/Nub.Lang/Frontend/Parsing/Parser.cs index eb0d730..1912b45 100644 --- a/src/lang/Nub.Lang/Frontend/Parsing/Parser.cs +++ b/src/lang/Nub.Lang/Frontend/Parsing/Parser.cs @@ -437,6 +437,31 @@ public class Parser { switch (symbolToken.Symbol) { + case Symbol.Func: + { + List parameters = []; + ExpectSymbol(Symbol.OpenParen); + while (!TryExpectSymbol(Symbol.CloseParen)) + { + var parameter = ParseFuncParameter(); + parameters.Add(parameter); + if (!TryExpectSymbol(Symbol.Comma) && Peek().TryGetValue(out var nextToken) && nextToken is not SymbolToken { Symbol: Symbol.CloseParen }) + { + _diagnostics.Add(Diagnostic + .Warning("Missing comma between function arguments") + .WithHelp("Add a ',' to separate arguments") + .At(nextToken) + .Build()); + } + } + + var returnType = TryExpectSymbol(Symbol.Colon) ? ParseType() : new NubVoidType(); + + var body = ParseBlock(); + + expr = new AnonymousFuncNode(GetTokensForNode(startIndex), parameters, body, returnType); + break; + } case Symbol.OpenParen: { var expression = ParseExpression(); diff --git a/src/lang/Nub.Lang/Frontend/Typing/TypeChecker.cs b/src/lang/Nub.Lang/Frontend/Typing/TypeChecker.cs index e7057d0..07f5822 100644 --- a/src/lang/Nub.Lang/Frontend/Typing/TypeChecker.cs +++ b/src/lang/Nub.Lang/Frontend/Typing/TypeChecker.cs @@ -14,6 +14,7 @@ public class TypeChecker private List _sourceFiles = []; private List _diagnostics = []; private NubType? _currentFunctionReturnType; + private List _anonymousFunctions = []; public DiagnosticsResult TypeCheck(List sourceFiles) { @@ -21,6 +22,7 @@ public class TypeChecker _diagnostics = []; _currentFunctionReturnType = null; _sourceFiles = sourceFiles; + _anonymousFunctions = []; var externFuncDefinitions = _sourceFiles .SelectMany(f => f.Definitions) @@ -50,7 +52,12 @@ public class TypeChecker foreach (var funcDef in _sourceFiles.SelectMany(f => f.Definitions).OfType()) { - TypeCheckFuncDef(funcDef); + TypeCheckFuncDef(funcDef.Parameters, funcDef.Body, funcDef.ReturnType); + } + + foreach (var anonymousFuncNode in _anonymousFunctions) + { + TypeCheckFuncDef(anonymousFuncNode.Parameters, anonymousFuncNode.Body, anonymousFuncNode.ReturnType); } return new DiagnosticsResult(_diagnostics); @@ -80,17 +87,17 @@ public class TypeChecker } } - private void TypeCheckFuncDef(LocalFuncDefinitionNode funcDef) + private void TypeCheckFuncDef(List parameters, BlockNode body, NubType returnType) { _variables.Clear(); - _currentFunctionReturnType = funcDef.ReturnType; + _currentFunctionReturnType = returnType; - foreach (var param in funcDef.Parameters) + foreach (var param in parameters) { _variables[param.Name] = param.Type; } - TypeCheckBlock(funcDef.Body); + TypeCheckBlock(body); } private void TypeCheckBlock(BlockNode block) @@ -343,6 +350,7 @@ public class TypeChecker var resultType = expression switch { AddressOfNode addressOf => TypeCheckAddressOf(addressOf), + AnonymousFuncNode anonymousFunc => TypeCheckAnonymousFunc(anonymousFunc), ArrayIndexAccessNode arrayIndex => TypeCheckArrayIndex(arrayIndex), ArrayInitializerNode arrayInitializer => TypeCheckArrayInitializer(arrayInitializer), LiteralNode literal => TypeCheckLiteral(literal, expectedType), @@ -365,6 +373,12 @@ public class TypeChecker return resultType; } + private NubType TypeCheckAnonymousFunc(AnonymousFuncNode anonymousFunc) + { + _anonymousFunctions.Add(anonymousFunc); + return new NubFuncType(anonymousFunc.ReturnType, anonymousFunc.Parameters.Select(p => p.Type).ToList()); + } + private NubType? TypeCheckLiteral(LiteralNode literal, NubType? expectedType = null) { if (expectedType != null)