diff --git a/build.sh b/build.sh index 5aaabed..88e5648 100755 --- a/build.sh +++ b/build.sh @@ -1,15 +1,21 @@ #!/bin/sh +set -e + mkdir -p out -dotnet run --project lang/Nub.Lang example out/out.asm +echo "setup..." -gcc -c -g -fno-stack-protector -fno-builtin std/baseline/gc.c -o out/gc.o -nasm -g -felf64 std/baseline/str_cmp.asm -o out/str_cmp.o +dotnet publish -c Release src/compiler/Nub.Lang -nasm -g -felf64 std/core/str_len.asm -o out/str_len.o -nasm -g -felf64 std/core/arr_size.asm -o out/arr_size.o -nasm -g -felf64 std/core/itoa.asm -o out/itoa.o +echo "compiling..." -nasm -g -felf64 out/out.asm -o out/out.o +nub example out/out.qbe -gcc -no-pie -nostartfiles -o out/program out/gc.o out/str_cmp.o out/str_len.o out/arr_size.o out/itoa.o out/out.o +nasm -g -felf64 src/runtime/runtime.asm -o out/runtime.o + +qbe out/out.qbe > out/out.s +gcc -c -g out/out.s -o out/out.o + +gcc -nostartfiles -o out/program out/runtime.o out/out.o + +echo "done..." diff --git a/debug.sh b/debug.sh index 3a13bf1..17f8778 100755 --- a/debug.sh +++ b/debug.sh @@ -1,4 +1,5 @@ #!/bin/sh +set -e ./clean.sh ./build.sh gdb -tui ./out/program diff --git a/example/c/bindings.nub b/example/c/bindings.nub new file mode 100644 index 0000000..9738661 --- /dev/null +++ b/example/c/bindings.nub @@ -0,0 +1,2 @@ +extern func puts(str: string) +extern func printf(fmt: string, ...args: any) \ No newline at end of file diff --git a/example/core/array.nub b/example/core/array.nub deleted file mode 100644 index dc7e6e3..0000000 --- a/example/core/array.nub +++ /dev/null @@ -1 +0,0 @@ -extern func arr_size(array: Array): int64; \ No newline at end of file diff --git a/example/core/print.nub b/example/core/print.nub deleted file mode 100644 index 624233a..0000000 --- a/example/core/print.nub +++ /dev/null @@ -1,38 +0,0 @@ -let SYS_WRITE = 1; -let STD_OUT = 1; -let STD_ERR = 2; - -func print(msg: String) { - syscall(SYS_WRITE, STD_OUT, msg, str_len(msg)); -} - -func print(value1: int64) { - print(itoa(value1)); -} - -func print(value2: bool) { - if value2 { - print("true"); - } else { - print("false"); - } -} - -func println() { - print("\n"); -} - -func println(msg: String) { - print(msg); - println(); -} - -func println(value3: bool) { - print(value3); - println(); -} - -func println(value4: int64) { - print(value4); - println(); -} diff --git a/example/core/string.nub b/example/core/string.nub deleted file mode 100644 index 3e0ce0b..0000000 --- a/example/core/string.nub +++ /dev/null @@ -1,2 +0,0 @@ -extern func str_len(msg: String): int64; -extern func itoa(value: int64): String; \ No newline at end of file diff --git a/example/program.nub b/example/program.nub index 1f2735b..eb398cf 100644 --- a/example/program.nub +++ b/example/program.nub @@ -1,16 +1,8 @@ -import "core"; +import c -struct Human { - let name: String; - let age: int64; -} +global func main(argc: i64, argv: i64) { + printf("args: %d, starts at %p\n", argc, argv) -func main() { - while true { - let x = new Human - { - name = "test", - age = 34958743 - }; - } + x: i8 = 320000 + printf("%d\n", x) } \ No newline at end of file diff --git a/lang/Nub.Lang/Backend/Custom/Generator.cs b/lang/Nub.Lang/Backend/Custom/Generator.cs deleted file mode 100644 index da3b950..0000000 --- a/lang/Nub.Lang/Backend/Custom/Generator.cs +++ /dev/null @@ -1,736 +0,0 @@ -using System.Text; -using Nub.Lang.Frontend.Parsing; - -namespace Nub.Lang.Backend.Custom; - -public class Generator -{ - private const string Entrypoint = "main"; - private const bool ZeroBasedIndexing = false; - - private readonly List _definitions; - private readonly SymbolTable _symbolTable; - private readonly StringBuilder _builder; - private readonly LabelFactory _labelFactory; - private readonly Stack<(string StartLabel, string EndLabel)> _loops; - - public Generator(List definitions) - { - _definitions = definitions; - _builder = new StringBuilder(); - _labelFactory = new LabelFactory(); - _symbolTable = new SymbolTable(_labelFactory); - _loops = []; - - foreach (var globalVariableDefinition in definitions.OfType()) - { - _symbolTable.DefineGlobalVariable(globalVariableDefinition); - } - - foreach (var funcDefinitionNode in definitions.OfType()) - { - _symbolTable.DefineFunc(funcDefinitionNode); - } - - foreach (var funcDefinitionNode in definitions.OfType()) - { - _symbolTable.DefineFunc(funcDefinitionNode); - } - } - - public string Generate() - { - _builder.AppendLine("global _start"); - _builder.AppendLine("extern gc_init"); - _builder.AppendLine("extern gc_alloc"); - _builder.AppendLine("extern str_cmp"); - - foreach (var externFuncDefinition in _definitions.OfType()) - { - _builder.AppendLine($"extern {externFuncDefinition.Name}"); - } - - _builder.AppendLine(); - _builder.AppendLine("section .text"); - - // TODO: Only add start label if entrypoint is present, otherwise assume library - var main = _symbolTable.ResolveLocalFunc(Entrypoint, []); - - _builder.AppendLine("_start:"); - _builder.AppendLine(" call gc_init"); - _builder.AppendLine($" call {main.StartLabel}"); - - _builder.AppendLine(main.ReturnType.HasValue - ? " mov rdi, rax" - : " mov rdi, 0"); - _builder.AppendLine(" mov rax, 60"); - _builder.AppendLine(" syscall"); - - foreach (var funcDefinition in _definitions.OfType()) - { - _builder.AppendLine(); - GenerateFuncDefinition(funcDefinition); - } - - _builder.AppendLine(""" - - eb6e_oob_error: - mov rax, 60 - mov rdi, 139 - syscall - """); - - _builder.AppendLine(); - _builder.AppendLine("section .data"); - - foreach (var str in _symbolTable.Strings) - { - _builder.AppendLine($" {str.Key}: db `{str.Value}`, 0"); - } - - Dictionary completed = []; - foreach (var globalVariableDefinition in _definitions.OfType()) - { - var variable = _symbolTable.ResolveGlobalVariable(globalVariableDefinition.Name); - var evaluated = EvaluateExpression(globalVariableDefinition.Value, completed); - _builder.AppendLine($" {variable.Identifier}: dq {evaluated}"); - completed[variable.Name] = evaluated; - } - - return _builder.ToString(); - } - - private string EvaluateExpression(ExpressionNode expression, Dictionary completed) - { - switch (expression) - { - case BinaryExpressionNode binaryExpression: - { - var left = EvaluateExpression(binaryExpression.Left, completed); - var right = EvaluateExpression(binaryExpression.Right, completed); - return binaryExpression.Operator switch - { - BinaryExpressionOperator.Equal => bool.Parse(left) == bool.Parse(right) ? "1" : "0", - BinaryExpressionOperator.NotEqual => bool.Parse(left) != bool.Parse(right) ? "1" : "0", - BinaryExpressionOperator.GreaterThan => long.Parse(left) > long.Parse(right) ? "1" : "0", - BinaryExpressionOperator.GreaterThanOrEqual => long.Parse(left) >= long.Parse(right) ? "1" : "0", - BinaryExpressionOperator.LessThan => long.Parse(left) < long.Parse(right) ? "1" : "0", - BinaryExpressionOperator.LessThanOrEqual => long.Parse(left) <= long.Parse(right) ? "1" : "0", - BinaryExpressionOperator.Plus => (long.Parse(left) + long.Parse(right)).ToString(), - BinaryExpressionOperator.Minus => (long.Parse(left) - long.Parse(right)).ToString(), - BinaryExpressionOperator.Multiply => (long.Parse(left) * long.Parse(right)).ToString(), - BinaryExpressionOperator.Divide => (long.Parse(left) / long.Parse(right)).ToString(), - _ => throw new ArgumentOutOfRangeException() - }; - } - case IdentifierNode identifier: - { - return completed[identifier.Identifier]; - } - case LiteralNode literal: - { - if (literal.Type is not PrimitiveType primitiveType) - { - throw new NotSupportedException("Global variable literals must be of a primitive type"); - } - - return primitiveType.Kind switch - { - PrimitiveTypeKind.Bool => bool.Parse(literal.Literal) ? "1" : "0", - PrimitiveTypeKind.Int64 or PrimitiveTypeKind.Int32 => $"{literal.Literal}", - _ => throw new ArgumentOutOfRangeException() - }; - } - default: - { - throw new InvalidOperationException("Global variables must be compile time consistant"); - } - } - } - - private void GenerateFuncDefinition(LocalFuncDefinitionNode node) - { - var func = _symbolTable.ResolveLocalFunc(node.Name, node.Parameters.Select(p => p.Type).ToList()); - - _builder.AppendLine($"{func.StartLabel}:"); - _builder.AppendLine(" push rbp"); - _builder.AppendLine(" mov rbp, rsp"); - _builder.AppendLine($" sub rsp, {func.StackAllocation}"); - - string[] registers = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"]; - - for (var i = 0; i < func.Parameters.Count; i++) - { - var parameter = func.ResolveLocalVariable(func.Parameters.ElementAt(i).Name); - if (i < registers.Length) - { - _builder.AppendLine($" mov [rbp - {parameter.Offset}], {registers[i]}"); - } - else - { - var stackOffset = 16 + (i - registers.Length) * 8; - _builder.AppendLine($" mov rax, [rbp + {stackOffset}]"); - _builder.AppendLine($" mov [rbp - {parameter.Offset}], rax"); - } - } - - GenerateBlock(node.Body, func); - - _builder.AppendLine($"{func.EndLabel}:"); - _builder.AppendLine(" mov rsp, rbp"); - _builder.AppendLine(" pop rbp"); - _builder.AppendLine(" ret"); - } - - private void GenerateBlock(BlockNode block, LocalFunc func) - { - foreach (var statement in block.Statements) - { - GenerateStatement(statement, func); - } - } - - private void GenerateStatement(StatementNode statement, LocalFunc func) - { - switch (statement) - { - case ArrayIndexAssignmentNode arrayIndexAssignment: - GenerateArrayIndexAssignment(arrayIndexAssignment, func); - break; - case BreakNode: - GenerateBreak(); - break; - case ContinueNode: - GenerateContinue(); - break; - case FuncCallStatementNode funcCallStatement: - GenerateFuncCall(funcCallStatement.FuncCall, func); - break; - case IfNode ifStatement: - GenerateIf(ifStatement, func); - break; - case ReturnNode @return: - GenerateReturn(@return, func); - break; - case SyscallStatementNode syscallStatement: - GenerateSyscall(syscallStatement.Syscall, func); - break; - case VariableAssignmentNode variableAssignment: - GenerateVariableAssignment(variableAssignment, func); - break; - case VariableReassignmentNode variableReassignment: - GenerateVariableReassignment(variableReassignment, func); - break; - case WhileNode whileStatement: - GenerateWhile(whileStatement, func); - break; - default: - throw new ArgumentOutOfRangeException(nameof(statement)); - } - } - - private void GenerateBreak() - { - _builder.AppendLine($" jmp {_loops.Peek().EndLabel}"); - } - - private void GenerateContinue() - { - _builder.AppendLine($" jmp {_loops.Peek().StartLabel}"); - } - - private void GenerateArrayIndexAssignment(ArrayIndexAssignmentNode arrayIndexAssignment, LocalFunc func) - { - GenerateExpression(arrayIndexAssignment.Value, func); - _builder.AppendLine(" push rax"); - GenerateArrayIndexPointerAccess(arrayIndexAssignment.Identifier, arrayIndexAssignment.Index, func); - _builder.AppendLine(" pop rdx"); - _builder.AppendLine(" mov [rax], rdx"); - } - - private void GenerateIf(IfNode ifStatement, LocalFunc func) - { - var endLabel = _labelFactory.Create(); - GenerateIf(ifStatement, endLabel, func); - _builder.AppendLine($"{endLabel}:"); - } - - private void GenerateIf(IfNode ifStatement, string endLabel, LocalFunc func) - { - var nextLabel = _labelFactory.Create(); - GenerateExpression(ifStatement.Condition, func); - _builder.AppendLine(" cmp rax, 0"); - _builder.AppendLine($" je {nextLabel}"); - GenerateBlock(ifStatement.Body, func); - _builder.AppendLine($" jmp {endLabel}"); - _builder.AppendLine($"{nextLabel}:"); - - if (ifStatement.Else.HasValue) - { - ifStatement.Else.Value.Match - ( - elseIfStatement => GenerateIf(elseIfStatement, endLabel, func), - elseStatement => GenerateBlock(elseStatement, func) - ); - } - } - - private void GenerateReturn(ReturnNode @return, LocalFunc func) - { - if (@return.Value.HasValue) - { - GenerateExpression(@return.Value.Value, func); - } - - _builder.AppendLine($" jmp {func.EndLabel}"); - } - - private void GenerateVariableAssignment(VariableAssignmentNode variableAssignment, LocalFunc func) - { - var variable = func.ResolveLocalVariable(variableAssignment.Name); - GenerateExpression(variableAssignment.Value, func); - _builder.AppendLine($" mov [rbp - {variable.Offset}], rax"); - } - - private void GenerateVariableReassignment(VariableReassignmentNode variableReassignment, LocalFunc func) - { - var variable = func.ResolveLocalVariable(variableReassignment.Name); - GenerateExpression(variableReassignment.Value, func); - _builder.AppendLine($" mov [rbp - {variable.Offset}], rax"); - } - - private void GenerateWhile(WhileNode whileStatement, LocalFunc func) - { - var startLabel = _labelFactory.Create(); - var endLabel = _labelFactory.Create(); - - _builder.AppendLine($"{startLabel}:"); - GenerateExpression(whileStatement.Condition, func); - _builder.AppendLine(" cmp rax, 0"); - _builder.AppendLine($" je {endLabel}"); - _loops.Push((startLabel, endLabel)); - GenerateBlock(whileStatement.Body, func); - _loops.Pop(); - _builder.AppendLine($" jmp {startLabel}"); - _builder.AppendLine($"{endLabel}:"); - } - - private void GenerateExpression(ExpressionNode expression, LocalFunc func) - { - switch (expression) - { - case ArrayIndexAccessNode arrayIndexAccess: - GenerateArrayIndexAccess(arrayIndexAccess, func); - break; - case ArrayInitializerNode arrayInitializer: - GenerateArrayInitializer(arrayInitializer); - break; - case BinaryExpressionNode binaryExpression: - GenerateBinaryExpression(binaryExpression, func); - break; - case FuncCallExpressionNode funcCallExpression: - GenerateFuncCall(funcCallExpression.FuncCall, func); - break; - case IdentifierNode identifier: - GenerateIdentifier(identifier, func); - break; - case LiteralNode literal: - GenerateLiteral(literal); - break; - case StructInitializerNode structInitializer: - GenerateStructInitializer(structInitializer, func); - break; - case StructMemberAccessorNode structMemberAccessor: - GenerateStructMemberAccessor(structMemberAccessor, func); - break; - case SyscallExpressionNode syscallExpression: - GenerateSyscall(syscallExpression.Syscall, func); - break; - default: - throw new ArgumentOutOfRangeException(nameof(expression)); - } - } - - private void GenerateStructMemberAccessor(StructMemberAccessorNode structMemberAccessor, LocalFunc func) - { - var variable = func.ResolveLocalVariable(structMemberAccessor.Members[0]); - - if (variable.Type is not StructType structType) - { - throw new Exception($"Cannot access struct member on {variable} since it is not a struct type"); - } - - _builder.AppendLine($" mov rax, [rbp - {variable.Offset}]"); - - Type prevMemberType = structType; - for (var i = 1; i < structMemberAccessor.Members.Count; i++) - { - if (prevMemberType is not StructType prevMemberStructType) - { - throw new Exception($"Cannot access {structMemberAccessor.Members[i]} on type {prevMemberType} because it is not a struct type"); - } - - var structDefinition = _definitions.OfType().FirstOrDefault(sd => sd.Name == prevMemberStructType.Name); - if (structDefinition == null) - { - throw new Exception($"Struct {prevMemberStructType} is not defined"); - } - - var member = structDefinition.Members.FirstOrDefault(m => m.Name == structMemberAccessor.Members[i]); - if (member == null) - { - throw new Exception($"Struct {prevMemberStructType} has no member with name {structMemberAccessor.Members[i]}"); - } - - var offset = structDefinition.Members.IndexOf(member); - _builder.AppendLine($" mov rax, [rax + {offset * 8}]"); - - prevMemberType = member.Type; - } - } - - private void GenerateArrayIndexAccess(ArrayIndexAccessNode arrayIndexAccess, LocalFunc func) - { - GenerateArrayIndexPointerAccess(arrayIndexAccess.Identifier, arrayIndexAccess.Index, func); - _builder.AppendLine(" mov rax, [rax]"); - } - - private void GenerateArrayInitializer(ArrayInitializerNode arrayInitializer) - { - _builder.AppendLine($" mov rdi, {8 + arrayInitializer.Length * 8}"); - _builder.AppendLine(" call gc_alloc"); - _builder.AppendLine($" mov qword [rax], {arrayInitializer.Length}"); - } - - private void GenerateBinaryExpression(BinaryExpressionNode binaryExpression, LocalFunc func) - { - GenerateExpression(binaryExpression.Left, func); - _builder.AppendLine(" push rax"); - GenerateExpression(binaryExpression.Right, func); - _builder.AppendLine(" mov rcx, rax"); - _builder.AppendLine(" pop rax"); - - switch (binaryExpression.Operator) - { - case BinaryExpressionOperator.Equal: - GenerateComparison(binaryExpression.Left.Type); - _builder.AppendLine(" sete al"); - _builder.AppendLine(" movzx rax, al"); - break; - case BinaryExpressionOperator.NotEqual: - GenerateComparison(binaryExpression.Left.Type); - _builder.AppendLine(" setne al"); - _builder.AppendLine(" movzx rax, al"); - break; - case BinaryExpressionOperator.GreaterThan: - GenerateComparison(binaryExpression.Left.Type); - _builder.AppendLine(" setg al"); - _builder.AppendLine(" movzx rax, al"); - break; - case BinaryExpressionOperator.GreaterThanOrEqual: - GenerateComparison(binaryExpression.Left.Type); - _builder.AppendLine(" setge al"); - _builder.AppendLine(" movzx rax, al"); - break; - case BinaryExpressionOperator.LessThan: - GenerateComparison(binaryExpression.Left.Type); - _builder.AppendLine(" setl al"); - _builder.AppendLine(" movzx rax, al"); - break; - case BinaryExpressionOperator.LessThanOrEqual: - GenerateComparison(binaryExpression.Left.Type); - _builder.AppendLine(" setle al"); - _builder.AppendLine(" movzx rax, al"); - break; - case BinaryExpressionOperator.Plus: - GenerateBinaryAddition(binaryExpression.Left.Type); - break; - case BinaryExpressionOperator.Minus: - GenerateBinarySubtraction(binaryExpression.Left.Type); - break; - case BinaryExpressionOperator.Multiply: - GenerateBinaryMultiplication(binaryExpression.Left.Type); - break; - case BinaryExpressionOperator.Divide: - GenerateBinaryDivision(binaryExpression.Left.Type); - break; - default: - throw new ArgumentOutOfRangeException(nameof(binaryExpression.Operator)); - } - } - - private void GenerateComparison(Type type) - { - switch (type) - { - case AnyType: - throw new InvalidOperationException($"Cannot compare type {type}"); - case ArrayType: - // compare pointers - _builder.AppendLine(" cmp rax, rcx"); - break; - case PrimitiveType: - _builder.AppendLine(" cmp rax, rcx"); - break; - case StringType: - _builder.AppendLine(" mov rdi, rax"); - _builder.AppendLine(" mov rsi, rcx"); - _builder.AppendLine(" call str_cmp"); - break; - default: - throw new ArgumentOutOfRangeException(nameof(type)); - } - } - - private void GenerateBinaryAddition(Type type) - { - if (type is not PrimitiveType primitiveType) - { - throw new InvalidOperationException("Addition can only be done on primitive types"); - } - - switch (primitiveType.Kind) - { - case PrimitiveTypeKind.Int64: - _builder.AppendLine(" add rax, rcx"); - break; - case PrimitiveTypeKind.Int32: - _builder.AppendLine(" add eax, ecx"); - break; - default: - throw new InvalidOperationException($"Invalid type {primitiveType.Kind}"); - } - } - - private void GenerateBinarySubtraction(Type type) - { - if (type is not PrimitiveType primitiveType) - { - throw new InvalidOperationException("Subtraction can only be done on primitive types"); - } - - switch (primitiveType.Kind) - { - case PrimitiveTypeKind.Int64: - _builder.AppendLine(" sub rax, rcx"); - break; - case PrimitiveTypeKind.Int32: - _builder.AppendLine(" sub eax, ecx"); - break; - default: - throw new InvalidOperationException($"Invalid type {primitiveType.Kind}"); - } - } - - private void GenerateBinaryMultiplication(Type type) - { - if (type is not PrimitiveType primitiveType) - { - throw new InvalidOperationException("Multiplication can only be done on primitive types"); - } - - switch (primitiveType.Kind) - { - case PrimitiveTypeKind.Int64: - _builder.AppendLine(" imul rcx"); - break; - case PrimitiveTypeKind.Int32: - _builder.AppendLine(" imul ecx"); - break; - default: - throw new InvalidOperationException($"Invalid type {primitiveType.Kind}"); - } - } - - private void GenerateBinaryDivision(Type type) - { - if (type is not PrimitiveType primitiveType) - { - throw new InvalidOperationException("Division can only be done on primitive types"); - } - - switch (primitiveType.Kind) - { - case PrimitiveTypeKind.Int64: - _builder.AppendLine(" cqo"); - _builder.AppendLine(" idiv rcx"); - break; - case PrimitiveTypeKind.Int32: - _builder.AppendLine(" cdq"); - _builder.AppendLine(" idiv ecx"); - break; - default: - throw new InvalidOperationException($"Invalid type {primitiveType.Kind}"); - } - } - - private void GenerateIdentifier(IdentifierNode identifier, LocalFunc func) - { - var variable = func.ResolveVariable(identifier.Identifier); - switch (variable) - { - case GlobalVariable globalVariable: - _builder.AppendLine($" mov rax, [{globalVariable.Identifier}]"); - break; - case LocalVariable localVariable: - { - _builder.AppendLine($" mov rax, [rbp - {localVariable.Offset}]"); - break; - } - default: - { - throw new ArgumentOutOfRangeException(nameof(variable)); - } - } - } - - private void GenerateLiteral(LiteralNode literal) - { - switch (literal.Type) - { - case StringType: - { - var label = _symbolTable.DefineString(literal.Literal); - _builder.AppendLine($" mov rax, {label}"); - break; - } - case PrimitiveType primitive: - { - switch (primitive.Kind) - { - case PrimitiveTypeKind.Bool: - _builder.AppendLine($" mov rax, {(bool.Parse(literal.Literal) ? "1" : "0")}"); - break; - case PrimitiveTypeKind.Int64: - _builder.AppendLine($" mov rax, {literal.Literal}"); - break; - case PrimitiveTypeKind.Int32: - _builder.AppendLine($" mov rax, {literal.Literal}"); - break; - default: - throw new Exception("Cannot convert literal to string"); - } - break; - } - default: - throw new ArgumentOutOfRangeException(); - } - } - - private void GenerateStructInitializer(StructInitializerNode structInitializer, LocalFunc func) - { - var structDefinition = _definitions - .OfType() - .FirstOrDefault(sd => sd.Name == structInitializer.StructType.Name); - - if (structDefinition == null) - { - throw new Exception($"Struct {structInitializer.StructType} is not defined"); - } - - _builder.AppendLine($" mov rdi, {structDefinition.Members.Count * 8}"); - _builder.AppendLine(" call gc_alloc"); - _builder.AppendLine(" mov rcx, rax"); - - foreach (var initializer in structInitializer.Initializers) - { - _builder.AppendLine(" push rcx"); - GenerateExpression(initializer.Value, func); - var index = structDefinition.Members.FindIndex(sd => sd.Name == initializer.Key); - if (index == -1) - { - throw new Exception($"Member {initializer.Key} is not defined on struct {structInitializer.StructType}"); - } - - _builder.AppendLine(" pop rcx"); - _builder.AppendLine($" mov [rcx + {index * 8}], rax"); - } - - foreach (var uninitializedMember in structDefinition.Members.Where(m => !structInitializer.Initializers.ContainsKey(m.Name))) - { - if (!uninitializedMember.Value.HasValue) - { - throw new Exception($"Struct {structInitializer.StructType} must be initializer with member {uninitializedMember.Name}"); - } - - _builder.AppendLine(" push rcx"); - GenerateExpression(uninitializedMember.Value.Value, func); - var index = structDefinition.Members.IndexOf(uninitializedMember); - _builder.AppendLine(" pop rcx"); - _builder.AppendLine($" mov [rcx + {index * 8}], rax"); - } - - _builder.AppendLine(" mov rax, rcx"); - } - - private void GenerateFuncCall(FuncCall funcCall, LocalFunc func) - { - var symbol = _symbolTable.ResolveFunc(funcCall.Name, funcCall.Parameters.Select(p => p.Type).ToList()); - string[] registers = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"]; - - for (var i = funcCall.Parameters.Count - 1; i >= 0; i--) - { - GenerateExpression(funcCall.Parameters.ElementAt(i), func); - _builder.AppendLine(" push rax"); - } - - var registerParameters = Math.Min(registers.Length, funcCall.Parameters.Count); - var stackParameters = funcCall.Parameters.Count - registerParameters; - - for (var i = 0; i < registerParameters; i++) - { - _builder.AppendLine($" pop {registers[i]}"); - } - - _builder.AppendLine($" call {symbol.StartLabel}"); - if (stackParameters != 0) - { - _builder.AppendLine($" add rsp, {stackParameters}"); - } - } - - private void GenerateSyscall(Syscall syscall, LocalFunc func) - { - string[] registers = ["rax", "rdi", "rsi", "rdx", "r10", "r8", "r9"]; - - foreach (var parameter in syscall.Parameters) - { - GenerateExpression(parameter, func); - _builder.AppendLine(" push rax"); - } - - for (var i = syscall.Parameters.Count - 1; i >= 0; i--) - { - _builder.AppendLine($" pop {registers[i]}"); - } - - _builder.AppendLine(" syscall"); - } - - private void GenerateArrayIndexPointerAccess(IdentifierNode identifier, ExpressionNode index, LocalFunc func) - { - GenerateExpression(index, func); - _builder.AppendLine(" push rax"); - GenerateIdentifier(identifier, func); - _builder.AppendLine(" pop rdx"); - - // rcx now holds the length of the array which we can use to check bounds - _builder.AppendLine(" mov rcx, [rax]"); - _builder.AppendLine(" cmp rdx, rcx"); - if (ZeroBasedIndexing) - { - _builder.AppendLine(" jge eb6e_oob_error"); - _builder.AppendLine(" cmp rdx, 0"); - } - else - { - _builder.AppendLine(" jg eb6e_oob_error"); - _builder.AppendLine(" cmp rdx, 1"); - } - _builder.AppendLine(" jl eb6e_oob_error"); - - _builder.AppendLine(" inc rdx"); - _builder.AppendLine(" shl rdx, 3"); - _builder.AppendLine(" add rax, rdx"); - } -} \ No newline at end of file diff --git a/lang/Nub.Lang/Backend/Custom/LabelFactory.cs b/lang/Nub.Lang/Backend/Custom/LabelFactory.cs deleted file mode 100644 index 6edd4f4..0000000 --- a/lang/Nub.Lang/Backend/Custom/LabelFactory.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Nub.Lang.Backend.Custom; - -public class LabelFactory -{ - private int _index; - public string Create() => $"label{++_index}"; -} \ No newline at end of file diff --git a/lang/Nub.Lang/Backend/Custom/SymbolTable.cs b/lang/Nub.Lang/Backend/Custom/SymbolTable.cs deleted file mode 100644 index ea0b193..0000000 --- a/lang/Nub.Lang/Backend/Custom/SymbolTable.cs +++ /dev/null @@ -1,258 +0,0 @@ -using Nub.Lang.Frontend.Parsing; - -namespace Nub.Lang.Backend.Custom; - -public class SymbolTable -{ - private readonly List _funcDefinitions = []; - private readonly List _globalVariables = []; - private readonly LabelFactory _labelFactory; - - public readonly Dictionary Strings = []; - - public SymbolTable(LabelFactory labelFactory) - { - _labelFactory = labelFactory; - } - - public string DefineString(string value) - { - var label = _labelFactory.Create(); - Strings.Add(label, value); - return label; - } - - public void DefineGlobalVariable(GlobalVariableDefinitionNode globalVariableDefinition) - { - var identifier = _labelFactory.Create(); - _globalVariables.Add(new GlobalVariable(globalVariableDefinition.Name, globalVariableDefinition.Value.Type, identifier)); - } - - public void DefineFunc(ExternFuncDefinitionNode externFuncDefinition) - { - var existing = _funcDefinitions - .FirstOrDefault(f => f - .SignatureMatches - ( - externFuncDefinition.Name, - externFuncDefinition.Parameters.Select(p => p.Type).ToList() - )); - - if (existing != null) - { - throw new Exception($"Func {existing} is already defined"); - } - - _funcDefinitions.Add(new ExternFunc(externFuncDefinition.Name, externFuncDefinition.Name, externFuncDefinition.Parameters, externFuncDefinition.ReturnType)); - } - - public void DefineFunc(LocalFuncDefinitionNode localFuncDefinition) - { - var existing = _funcDefinitions - .FirstOrDefault(f => f - .SignatureMatches - ( - localFuncDefinition.Name, - localFuncDefinition.Parameters.Select(p => p.Type).ToList() - )); - - if (existing != null) - { - throw new Exception($"Func {existing} is already defined"); - } - - var startLabel = _labelFactory.Create(); - var endLabel = _labelFactory.Create(); - _funcDefinitions.Add(new LocalFunc(localFuncDefinition.Name, startLabel, endLabel, localFuncDefinition.Parameters, localFuncDefinition.ReturnType, _globalVariables.Concat(ResolveFuncVariables(localFuncDefinition)).ToList())); - } - - private static List ResolveFuncVariables(LocalFuncDefinitionNode localFuncDefinition) - { - var offset = 0; - List variables = []; - - foreach (var parameter in localFuncDefinition.Parameters) - { - offset += 8; - variables.Add(new LocalVariable(parameter.Name, parameter.Type, offset)); - } - - ResolveBlockVariables(localFuncDefinition.Body, variables, offset); - - return variables; - } - - private static int ResolveBlockVariables(BlockNode block, List variables, int offset) - { - foreach (var statement in block.Statements) - { - switch (statement) - { - case IfNode ifStatement: - { - offset = ResolveBlockVariables(ifStatement.Body, variables, offset); - if (ifStatement.Else.HasValue) - { - ifStatement.Else.Value.Match - ( - elseIfStatement => offset = ResolveBlockVariables(elseIfStatement.Body, variables, offset), - elseStatement => offset = ResolveBlockVariables(elseStatement, variables, offset) - ); - } - break; - } - case WhileNode whileStatement: - { - offset = ResolveBlockVariables(whileStatement.Body, variables, offset); - break; - } - case VariableAssignmentNode variableAssignment: - { - offset += 8; - variables.Add(new LocalVariable(variableAssignment.Name, variableAssignment.Value.Type, offset)); - break; - } - } - } - - return offset; - } - - public Func ResolveFunc(string name, List parameterTypes) - { - var func = _funcDefinitions.FirstOrDefault(f => f.SignatureMatches(name, parameterTypes)); - if (func == null) - { - throw new Exception($"Func {name}({string.Join(", ", parameterTypes)}) is not defined"); - } - - return func; - } - - public LocalFunc ResolveLocalFunc(string name, List parameterTypes) - { - var func = ResolveFunc(name, parameterTypes); - if (func is not LocalFunc localFunc) - { - throw new Exception($"Func {func} is not a local func"); - } - return localFunc; - } - - public ExternFunc ResolveExternFunc(string name, List parameterTypes) - { - var func = ResolveFunc(name, parameterTypes); - if (func is not ExternFunc externFunc) - { - throw new Exception($"Func {func} is not an extern func"); - } - return externFunc; - } - - public GlobalVariable ResolveGlobalVariable(string name) - { - var variable = _globalVariables.FirstOrDefault(v => v.Name == name); - if (variable == null) - { - throw new Exception($"Global variable {name} is not defined"); - } - - return variable; - } -} - -public abstract class Variable(string name, Type type) -{ - public string Name { get; } = name; - public Type Type { get; } = type; - - public override string ToString() => $"{Name}: {Type}"; -} - -public class LocalVariable(string name, Type type, int offset) : Variable(name, type) -{ - public int Offset { get; } = offset; -} - -public class GlobalVariable(string name, Type type, string identifier) : Variable(name, type) -{ - public string Identifier { get; } = identifier; -} - -public abstract class Func -{ - protected Func(string name, string startLabel, List parameters, Optional returnType) - { - Name = name; - Parameters = parameters; - ReturnType = returnType; - StartLabel = startLabel; - } - - public string Name { get; } - public string StartLabel { get; } - public List Parameters { get; } - public Optional ReturnType { get; } - - public bool SignatureMatches(string name, List parameterTypes) - { - if (Name != name) return false; - if (Parameters.Count != parameterTypes.Count) return false; - - for (var i = 0; i < parameterTypes.Count; i++) - { - if (!Parameters[i].Type.IsAssignableTo(parameterTypes[i])) return false; - } - - return true; - } - - public override string ToString() => $"{Name}({string.Join(", ", Parameters.Select(p => p.ToString()))}){(ReturnType.HasValue ? ": " + ReturnType.Value : "")}"; -} - -public class ExternFunc : Func -{ - public ExternFunc(string name, string startLabel, List parameters, Optional returnType) : base(name, startLabel, parameters, returnType) - { - } -} - -public class LocalFunc : Func -{ - public LocalFunc(string name, string startLabel, string endLabel, List parameters, Optional returnType, List variables) : base(name, startLabel, parameters, returnType) - { - EndLabel = endLabel; - Variables = variables; - } - - public string EndLabel { get; } - public List Variables { get; } - public int StackAllocation => Variables.OfType().Sum(variable => variable.Offset); - - public Variable ResolveVariable(string name) - { - var variable = Variables.FirstOrDefault(v => v.Name == name); - if (variable == null) - { - throw new Exception($"Variable {name} is not defined"); - } - - return variable; - } - - public LocalVariable ResolveLocalVariable(string name) - { - var variable = Variables.FirstOrDefault(v => v.Name == name); - if (variable == null) - { - throw new Exception($"Variable {name} is not defined"); - } - - if (variable is not LocalVariable localVariable) - { - throw new Exception($"Variable {name} is not a local variable"); - } - - return localVariable; - } -} \ No newline at end of file diff --git a/lang/Nub.Lang/Frontend/Lexing/LiteralToken.cs b/lang/Nub.Lang/Frontend/Lexing/LiteralToken.cs deleted file mode 100644 index 7128b1a..0000000 --- a/lang/Nub.Lang/Frontend/Lexing/LiteralToken.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Nub.Lang.Frontend.Lexing; - -public class LiteralToken(Type type, string value) : Token -{ - public Type Type { get; } = type; - public string Value { get; } = value; -} \ No newline at end of file diff --git a/lang/Nub.Lang/Frontend/Parsing/ArrayIndexAccessNode.cs b/lang/Nub.Lang/Frontend/Parsing/ArrayIndexAccessNode.cs deleted file mode 100644 index cecdbee..0000000 --- a/lang/Nub.Lang/Frontend/Parsing/ArrayIndexAccessNode.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Nub.Lang.Frontend.Parsing; - -public class ArrayIndexAccessNode(IdentifierNode identifier, ExpressionNode index) : ExpressionNode -{ - public IdentifierNode Identifier { get; } = identifier; - public ExpressionNode Index { get; } = index; - - public override string ToString() => $"{Identifier}[{Index}]"; -} \ No newline at end of file diff --git a/lang/Nub.Lang/Frontend/Parsing/ArrayIndexAssignmentNode.cs b/lang/Nub.Lang/Frontend/Parsing/ArrayIndexAssignmentNode.cs deleted file mode 100644 index bcee92a..0000000 --- a/lang/Nub.Lang/Frontend/Parsing/ArrayIndexAssignmentNode.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Nub.Lang.Frontend.Parsing; - -public class ArrayIndexAssignmentNode(IdentifierNode identifier, ExpressionNode index, ExpressionNode value) : StatementNode -{ - public IdentifierNode Identifier { get; } = identifier; - public ExpressionNode Index { get; } = index; - public ExpressionNode Value { get; } = value; -} \ No newline at end of file diff --git a/lang/Nub.Lang/Frontend/Parsing/ArrayInitializerNode.cs b/lang/Nub.Lang/Frontend/Parsing/ArrayInitializerNode.cs deleted file mode 100644 index 7f7414b..0000000 --- a/lang/Nub.Lang/Frontend/Parsing/ArrayInitializerNode.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Nub.Lang.Frontend.Parsing; - -public class ArrayInitializerNode(long length, Type innerType) : ExpressionNode -{ - public long Length { get; } = length; - public Type InnerType { get; } = innerType; -} \ No newline at end of file diff --git a/lang/Nub.Lang/Frontend/Parsing/GlobalVariableDefinitionNode.cs b/lang/Nub.Lang/Frontend/Parsing/GlobalVariableDefinitionNode.cs deleted file mode 100644 index 04a69d2..0000000 --- a/lang/Nub.Lang/Frontend/Parsing/GlobalVariableDefinitionNode.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Nub.Lang.Frontend.Parsing; - -public class GlobalVariableDefinitionNode(string name, ExpressionNode value) : DefinitionNode -{ - public string Name { get; } = name; - public ExpressionNode Value { get; } = value; -} \ No newline at end of file diff --git a/lang/Nub.Lang/Frontend/Parsing/LiteralNode.cs b/lang/Nub.Lang/Frontend/Parsing/LiteralNode.cs deleted file mode 100644 index a5065d8..0000000 --- a/lang/Nub.Lang/Frontend/Parsing/LiteralNode.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Nub.Lang.Frontend.Parsing; - -public class LiteralNode(string literal, Type type) : ExpressionNode -{ - public string Literal { get; } = literal; - public Type LiteralType { get; } = type; -} \ No newline at end of file diff --git a/lang/Nub.Lang/Frontend/Parsing/StructDefinitionNode.cs b/lang/Nub.Lang/Frontend/Parsing/StructDefinitionNode.cs deleted file mode 100644 index d4eaada..0000000 --- a/lang/Nub.Lang/Frontend/Parsing/StructDefinitionNode.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Nub.Lang.Frontend.Parsing; - -public class StructDefinitionNode(string name, List members) : DefinitionNode -{ - public string Name { get; } = name; - public List Members { get; } = members; -} \ No newline at end of file diff --git a/lang/Nub.Lang/Frontend/Parsing/StructInitializerNode.cs b/lang/Nub.Lang/Frontend/Parsing/StructInitializerNode.cs deleted file mode 100644 index a04df84..0000000 --- a/lang/Nub.Lang/Frontend/Parsing/StructInitializerNode.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Nub.Lang.Frontend.Parsing; - -public class StructInitializerNode(StructType structType, Dictionary initializers) : ExpressionNode -{ - public StructType StructType { get; } = structType; - public Dictionary Initializers { get; } = initializers; -} \ No newline at end of file diff --git a/lang/Nub.Lang/Frontend/Parsing/StructMemberAccessorNode.cs b/lang/Nub.Lang/Frontend/Parsing/StructMemberAccessorNode.cs deleted file mode 100644 index a265b62..0000000 --- a/lang/Nub.Lang/Frontend/Parsing/StructMemberAccessorNode.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Nub.Lang.Frontend.Parsing; - -public class StructMemberAccessorNode(List members) : ExpressionNode -{ - public List Members { get; } = members; -} \ No newline at end of file diff --git a/lang/Nub.Lang/Frontend/Parsing/Syscall.cs b/lang/Nub.Lang/Frontend/Parsing/Syscall.cs deleted file mode 100644 index 7c0ab16..0000000 --- a/lang/Nub.Lang/Frontend/Parsing/Syscall.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Nub.Lang.Frontend.Parsing; - -public class Syscall(List parameters) -{ - public List Parameters { get; } = parameters; -} \ No newline at end of file diff --git a/lang/Nub.Lang/Frontend/Parsing/SyscallExpressionNode.cs b/lang/Nub.Lang/Frontend/Parsing/SyscallExpressionNode.cs deleted file mode 100644 index 0426ead..0000000 --- a/lang/Nub.Lang/Frontend/Parsing/SyscallExpressionNode.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Nub.Lang.Frontend.Parsing; - -public class SyscallExpressionNode(Syscall syscall) : ExpressionNode -{ - public Syscall Syscall { get; } = syscall; -} \ No newline at end of file diff --git a/lang/Nub.Lang/Frontend/Parsing/SyscallStatementNode.cs b/lang/Nub.Lang/Frontend/Parsing/SyscallStatementNode.cs deleted file mode 100644 index 48fedb1..0000000 --- a/lang/Nub.Lang/Frontend/Parsing/SyscallStatementNode.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Nub.Lang.Frontend.Parsing; - -public class SyscallStatementNode(Syscall syscall) : StatementNode -{ - public Syscall Syscall { get; } = syscall; -} \ No newline at end of file diff --git a/lang/Nub.Lang/Frontend/Parsing/VariableAssignmentNode.cs b/lang/Nub.Lang/Frontend/Parsing/VariableAssignmentNode.cs deleted file mode 100644 index e76cb63..0000000 --- a/lang/Nub.Lang/Frontend/Parsing/VariableAssignmentNode.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Nub.Lang.Frontend.Parsing; - -public class VariableAssignmentNode(string name, ExpressionNode value) : StatementNode -{ - public string Name { get; } = name; - public ExpressionNode Value { get; } = value; -} \ No newline at end of file diff --git a/lang/Nub.Lang/Frontend/Parsing/VariableReassignmentNode.cs b/lang/Nub.Lang/Frontend/Parsing/VariableReassignmentNode.cs deleted file mode 100644 index 74fa9a4..0000000 --- a/lang/Nub.Lang/Frontend/Parsing/VariableReassignmentNode.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Nub.Lang.Frontend.Parsing; - -public class VariableReassignmentNode(string name, ExpressionNode value) : StatementNode -{ - public string Name { get; } = name; - public ExpressionNode Value { get; } = value; -} \ No newline at end of file diff --git a/lang/Nub.Lang/FuncParameter.cs b/lang/Nub.Lang/FuncParameter.cs deleted file mode 100644 index cfbb556..0000000 --- a/lang/Nub.Lang/FuncParameter.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Nub.Lang; - -public class FuncParameter(string name, Type type) -{ - public string Name { get; } = name; - public Type Type { get; } = type; - - public override string ToString() => $"{Name}: {Type}"; -} \ No newline at end of file diff --git a/lang/Nub.Lang/Type.cs b/lang/Nub.Lang/Type.cs deleted file mode 100644 index 76b5a3b..0000000 --- a/lang/Nub.Lang/Type.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -namespace Nub.Lang; - -public abstract class Type -{ - public virtual bool IsAssignableTo(Type otherType) - { - return this == otherType || otherType is AnyType; - } - - public override bool Equals(object? obj) - { - if (ReferenceEquals(this, obj)) return true; - if (obj is not Type otherType) return false; - return Equals(otherType); - } - - protected abstract bool Equals(Type other); - public abstract override int GetHashCode(); - - public static bool operator == (Type? left, Type? right) - { - if (left is null && right is null) return true; - if (left is null || right is null) return false; - return ReferenceEquals(left, right) || left.Equals(right); - } - - public static bool operator !=(Type? left, Type? right) => !(left == right); -} - -public class AnyType : Type -{ - protected override bool Equals(Type other) => other is AnyType; - public override int GetHashCode() => nameof(AnyType).GetHashCode(); - public override string ToString() => "Any"; -} - -public class PrimitiveType(PrimitiveTypeKind kind) : Type -{ - // TODO: This should be looked at more in the future - public override bool IsAssignableTo(Type otherType) - { - if (base.IsAssignableTo(otherType)) return true; - - if (otherType is PrimitiveType otherPrimitive) - { - return (Kind, otherPrimitive.Kind) switch - { - (PrimitiveTypeKind.Int32, PrimitiveTypeKind.Int64) => true, - _ => false - }; - } - - return false; - } - - public static bool TryParse(string value, [NotNullWhen(true)] out PrimitiveType? result) - { - result = value switch - { - "bool" => new PrimitiveType(PrimitiveTypeKind.Bool), - "int64" => new PrimitiveType(PrimitiveTypeKind.Int64), - "int32" => new PrimitiveType(PrimitiveTypeKind.Int32), - _ => null - }; - - return result != null; - } - - public PrimitiveTypeKind Kind { get; } = kind; - - protected override bool Equals(Type other) => other is PrimitiveType primitiveType && Kind == primitiveType.Kind; - public override int GetHashCode() => Kind.GetHashCode(); - public override string ToString() => Kind.ToString(); -} - -public enum PrimitiveTypeKind -{ - Bool, - Int64, - Int32, -} - -public class StringType : Type -{ - protected override bool Equals(Type other) => other is StringType; - public override int GetHashCode() => nameof(StringType).GetHashCode(); - public override string ToString() => "String"; -} - -public class ArrayType(Type innerType) : Type -{ - public Type InnerType { get; } = innerType; - - public override bool IsAssignableTo(Type otherType) - { - if (otherType is ArrayType arrayType && arrayType.InnerType.IsAssignableTo(InnerType)) return true; - return base.IsAssignableTo(otherType); - } - - protected override bool Equals(Type other) => other is ArrayType at && InnerType.Equals(at.InnerType); - public override int GetHashCode() => HashCode.Combine(InnerType); - public override string ToString() => $"Array<{InnerType}>"; -} - -public class StructType(string name) : Type -{ - public string Name { get; } = name; - - protected override bool Equals(Type other) => other is StructType classType && Name == classType.Name; - public override int GetHashCode() => Name.GetHashCode(); - public override string ToString() => Name; -} \ No newline at end of file diff --git a/lang/.gitignore b/src/compiler/.gitignore similarity index 100% rename from lang/.gitignore rename to src/compiler/.gitignore diff --git a/lang/.idea/.idea.Nub.Lang/.idea/.gitignore b/src/compiler/.idea/.idea.Nub.Lang/.idea/.gitignore similarity index 100% rename from lang/.idea/.idea.Nub.Lang/.idea/.gitignore rename to src/compiler/.idea/.idea.Nub.Lang/.idea/.gitignore diff --git a/lang/.idea/.idea.Nub.Lang/.idea/.name b/src/compiler/.idea/.idea.Nub.Lang/.idea/.name similarity index 100% rename from lang/.idea/.idea.Nub.Lang/.idea/.name rename to src/compiler/.idea/.idea.Nub.Lang/.idea/.name diff --git a/lang/.idea/.idea.Nub.Lang/.idea/codeStyles/codeStyleConfig.xml b/src/compiler/.idea/.idea.Nub.Lang/.idea/codeStyles/codeStyleConfig.xml similarity index 100% rename from lang/.idea/.idea.Nub.Lang/.idea/codeStyles/codeStyleConfig.xml rename to src/compiler/.idea/.idea.Nub.Lang/.idea/codeStyles/codeStyleConfig.xml diff --git a/lang/.idea/.idea.Nub.Lang/.idea/encodings.xml b/src/compiler/.idea/.idea.Nub.Lang/.idea/encodings.xml similarity index 100% rename from lang/.idea/.idea.Nub.Lang/.idea/encodings.xml rename to src/compiler/.idea/.idea.Nub.Lang/.idea/encodings.xml diff --git a/lang/.idea/.idea.Nub.Lang/.idea/indexLayout.xml b/src/compiler/.idea/.idea.Nub.Lang/.idea/indexLayout.xml similarity index 74% rename from lang/.idea/.idea.Nub.Lang/.idea/indexLayout.xml rename to src/compiler/.idea/.idea.Nub.Lang/.idea/indexLayout.xml index 1479e46..4a53b21 100644 --- a/lang/.idea/.idea.Nub.Lang/.idea/indexLayout.xml +++ b/src/compiler/.idea/.idea.Nub.Lang/.idea/indexLayout.xml @@ -2,7 +2,8 @@ - ../std + ../../example + ../core diff --git a/lang/.idea/.idea.Nub.Lang/.idea/vcs.xml b/src/compiler/.idea/.idea.Nub.Lang/.idea/vcs.xml similarity index 63% rename from lang/.idea/.idea.Nub.Lang/.idea/vcs.xml rename to src/compiler/.idea/.idea.Nub.Lang/.idea/vcs.xml index 6c0b863..b2bdec2 100644 --- a/lang/.idea/.idea.Nub.Lang/.idea/vcs.xml +++ b/src/compiler/.idea/.idea.Nub.Lang/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/lang/Nub.Lang.sln b/src/compiler/Nub.Lang.sln similarity index 100% rename from lang/Nub.Lang.sln rename to src/compiler/Nub.Lang.sln diff --git a/src/compiler/Nub.Lang/Backend/Generator.cs b/src/compiler/Nub.Lang/Backend/Generator.cs new file mode 100644 index 0000000..9e72769 --- /dev/null +++ b/src/compiler/Nub.Lang/Backend/Generator.cs @@ -0,0 +1,1189 @@ +using System.Globalization; +using System.Text; +using Nub.Lang.Frontend.Parsing; + +namespace Nub.Lang.Backend; + +public class Generator +{ + private readonly List _definitions; + private readonly StringBuilder _builder = new(); + private readonly Dictionary _prefixIndexes = new(); + private readonly Dictionary _variables = new(); + private readonly List _strings = []; + private readonly Stack _breakLabels = new(); + private readonly Stack _continueLabels = new(); + private bool _codeIsReachable = true; + private LocalFuncDefinitionNode? _currentFuncDefininition; + + public Generator(List definitions) + { + _definitions = definitions; + } + + public string Generate() + { + foreach (var structDefinition in _definitions.OfType()) + { + GenerateStructDefinition(structDefinition); + _builder.AppendLine(); + } + + foreach (var funcDefinition in _definitions.OfType()) + { + GenerateFuncDefinition(funcDefinition); + _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: + return "l"; + case PrimitiveTypeKind.Any: + throw new NotSupportedException("Cannot convert 'any' type to QBE type"); + 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 NubCustomType nubCustomType: + { + 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: + throw new NotSupportedException("Cannot convert 'any' type to QBE type"); + 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 NubCustomType nubCustomType: + { + return ":" + nubCustomType.Name; + } + 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: + return "l"; + case PrimitiveTypeKind.Any: + throw new NotSupportedException("Cannot convert 'any' type to QBE type"); + 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 NubCustomType nubCustomType: + { + return ":" + nubCustomType.Name; + } + default: + { + throw new NotImplementedException(); + } + } + } + + private static 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 NubCustomType nubCustomType: + { + return 8; + } + default: + { + throw new NotImplementedException(); + } + } + } + + private void GenerateFuncDefinition(LocalFuncDefinitionNode node) + { + _currentFuncDefininition = node; + _variables.Clear(); + + if (node.Global) + { + _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(node.Name); + + var parameterStrings = node.Parameters.Select(parameter => parameter.Variadic ? "..." : $"{FQT(parameter.Type)} %{parameter.Name}"); + + _builder.AppendLine($"({string.Join(", ", parameterStrings)}) {{"); + _builder.AppendLine("@start"); + + foreach (var parameter in node.Parameters) + { + var parameterName = parameter.Name; + + switch (FQT(parameter.Type)) + { + case "sb": + parameterName = GenName("c"); + _builder.AppendLine($" %{parameterName} =w extsb %{parameter.Name}"); + break; + case "ub": + parameterName = GenName("c"); + _builder.AppendLine($" %{parameterName} =w extub %{parameter.Name}"); + break; + case "sh": + parameterName = GenName("c"); + _builder.AppendLine($" %{parameterName} =w extsh %{parameter.Name}"); + break; + case "uh": + parameterName = GenName("c"); + _builder.AppendLine($" %{parameterName} =w extuh %{parameter.Name}"); + break; + } + + _variables.Add(parameter.Name, new Variable + { + Identifier = $"%{parameterName}", + Type = parameter.Type + }); + } + + GenerateBlock(node.Body); + + if (node.Body.Statements.Last() is not ReturnNode) + { + if (!node.ReturnType.HasValue && node.Name == "main") + { + _builder.AppendLine(" ret 0"); + } + else if (!node.ReturnType.HasValue) + { + _builder.AppendLine(" ret"); + } + } + + _builder.AppendLine("}"); + _currentFuncDefininition = null; + } + + 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 parameterDefinitions = _definitions + .OfType() + .FirstOrDefault(d => d.Name == funcCall.Name) + ?.Parameters; + + parameterDefinitions ??= _definitions + .OfType() + .FirstOrDefault(d => d.Name == funcCall.Name) + ?.Parameters; + + if (parameterDefinitions == null) + { + throw new Exception($"Unknown function {funcCall}"); + } + + var parameterStrings = new List(); + + for (var i = 0; i < funcCall.Parameters.Count; i++) + { + if (i < parameterDefinitions.Count && parameterDefinitions[i].Variadic) + { + parameterStrings.Add("..."); + } + + NubType expectedType; + if (i < parameterDefinitions.Count) + { + expectedType = parameterDefinitions[i].Type; + } + else if (parameterDefinitions[^1].Variadic) + { + expectedType = parameterDefinitions[^1].Type; + } + else + { + throw new Exception($"Parameters for func {funcCall} does not not match"); + } + + var parameter = funcCall.Parameters[i]; + var parameterOutput = GenerateExpression(parameter); + var result = GenerateTypeConversion(parameterOutput, parameter.Type, expectedType); + + var qbeParameterType = SQT(expectedType.Equals(NubPrimitiveType.Any) ? parameter.Type : expectedType); + parameterStrings.Add($"{qbeParameterType} {result}"); + } + + return $"call ${funcCall.Name}({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 = GenName("true"); + var falseLabel = GenName("false"); + var endLabel = GenName("endif"); + + 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) + { + if (!_currentFuncDefininition!.ReturnType.HasValue) + { + throw new Exception("Cannot return a value when function does not have a return value"); + } + + var result = GenerateExpression(@return.Value.Value); + var converted = GenerateTypeConversion(result, @return.Value.Value.Type, _currentFuncDefininition.ReturnType.Value); + _builder.AppendLine($" ret {converted}"); + } + else + { + _builder.AppendLine(" ret"); + } + } + + private void GenerateVariableAssignment(VariableAssignmentNode variableAssignment) + { + var result = GenerateExpression(variableAssignment.Value); + var variableType = variableAssignment.Value.Type; + + if (variableAssignment.ExplicitType.HasValue) + { + result = GenerateTypeConversion(result, variableType, variableAssignment.ExplicitType.Value); + variableType = variableAssignment.ExplicitType.Value; + } + _variables[variableAssignment.Name] = new Variable + { + Identifier = result, + Type = variableType + }; + } + + private void GenerateWhile(WhileNode whileStatement) + { + var conditionLabel = GenName("condition"); + var iterationLabel = GenName("iteration"); + var endLabel = GenName("endloop"); + + _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) + { + switch (expression) + { + case BinaryExpressionNode binaryExpression: + return GenerateBinaryExpression(binaryExpression); + case FuncCallExpressionNode funcCallExpression: + return GenerateExpressionFuncCall(funcCallExpression); + case IdentifierNode identifier: + return GenerateIdentifier(identifier); + case LiteralNode literal: + return GenerateLiteral(literal); + case StructInitializerNode structInitializer: + return GenerateStructInitializer(structInitializer); + case StructFieldAccessorNode structMemberAccessor: + return GenerateStructFieldAccessor(structMemberAccessor); + default: + throw new ArgumentOutOfRangeException(nameof(expression)); + } + } + + private string GenerateStructFieldAccessor(StructFieldAccessorNode structFieldAccessor) + { + var structType = structFieldAccessor.Struct.Type; + var structDefinition = _definitions + .OfType() + .FirstOrDefault(s => s.Name == structType.Name); + + if (structDefinition == null) + { + throw new Exception($"Struct {structType.Name} is not defined"); + } + + var @struct = GenerateExpression(structFieldAccessor.Struct); + + var fieldIndex = -1; + for (var i = 0; i < structDefinition.Fields.Count; i++) + { + if (structDefinition.Fields[i].Name == structFieldAccessor.Field) + { + fieldIndex = i; + break; + } + } + + if (fieldIndex == -1) + { + throw new Exception($"Field {structFieldAccessor.Field} is not defined in struct {structType.Name}"); + } + + var offsetLabel = GenName("offset"); + _builder.AppendLine($" %{offsetLabel} =l add {@struct}, {fieldIndex * QbeTypeSize(structFieldAccessor.Type)}"); + + var outputLabel = GenName("field"); + _builder.AppendLine($" %{outputLabel} ={SQT(structFieldAccessor.Type)} load{SQT(structFieldAccessor.Type)} %{offsetLabel}"); + + return $"%{outputLabel}"; + } + + private string GenerateBinaryExpression(BinaryExpressionNode binaryExpression) + { + var left = GenerateExpression(binaryExpression.Left); + var right = GenerateExpression(binaryExpression.Right); + var outputLabel = GenName(); + + switch (binaryExpression.Operator) + { + case BinaryExpressionOperator.Equal: + { + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64)) + { + _builder.AppendLine($" %{outputLabel} =w ceql {left}, {right}"); + return $"%{outputLabel}"; + } + + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32)) + { + _builder.AppendLine($" %{outputLabel} =w ceqw {left}, {right}"); + return $"%{outputLabel}"; + } + + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.String)) + { + _builder.AppendLine($" %{outputLabel} =w call $nub_strcmp(l {left}, l {right})"); + return $"%{outputLabel}"; + } + + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.Bool)) + { + _builder.AppendLine($" %{outputLabel} =w ceqw {left}, {right}"); + return $"%{outputLabel}"; + } + + break; + } + case BinaryExpressionOperator.NotEqual: + { + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64)) + { + _builder.AppendLine($" %{outputLabel} =w cnel {left}, {right}"); + return $"%{outputLabel}"; + } + + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32)) + { + _builder.AppendLine($" %{outputLabel} =w cnew {left}, {right}"); + return $"%{outputLabel}"; + } + + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.String)) + { + _builder.AppendLine($" %{outputLabel} =w call $nub_strcmp(l {left}, l {right})"); + _builder.AppendLine($" %{outputLabel} =w xor %{outputLabel}, 1"); + return $"%{outputLabel}"; + } + + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.Bool)) + { + _builder.AppendLine($" %{outputLabel} =w cnew {left}, {right}"); + return $"%{outputLabel}"; + } + + break; + } + case BinaryExpressionOperator.GreaterThan: + { + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64)) + { + _builder.AppendLine($" %{outputLabel} =w csgtl {left}, {right}"); + return $"%{outputLabel}"; + } + + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32)) + { + _builder.AppendLine($" %{outputLabel} =w csgtw {left}, {right}"); + return $"%{outputLabel}"; + } + + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.Bool)) + { + _builder.AppendLine($" %{outputLabel} =w csgtw {left}, {right}"); + return $"%{outputLabel}"; + } + + break; + } + case BinaryExpressionOperator.GreaterThanOrEqual: + { + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64)) + { + _builder.AppendLine($" %{outputLabel} =w csgel {left}, {right}"); + return $"%{outputLabel}"; + } + + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32)) + { + _builder.AppendLine($" %{outputLabel} =w csgew {left}, {right}"); + return $"%{outputLabel}"; + } + + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.Bool)) + { + _builder.AppendLine($" %{outputLabel} =w csgew {left}, {right}"); + return $"%{outputLabel}"; + } + + break; + } + case BinaryExpressionOperator.LessThan: + { + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64)) + { + _builder.AppendLine($" %{outputLabel} =w csltl {left}, {right}"); + return $"%{outputLabel}"; + } + + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32)) + { + _builder.AppendLine($" %{outputLabel} =w csltw {left}, {right}"); + return $"%{outputLabel}"; + } + + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.Bool)) + { + _builder.AppendLine($" %{outputLabel} =w csltw {left}, {right}"); + return $"%{outputLabel}"; + } + + break; + } + case BinaryExpressionOperator.LessThanOrEqual: + { + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64)) + { + _builder.AppendLine($" %{outputLabel} =w cslel {left}, {right}"); + return $"%{outputLabel}"; + } + + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32)) + { + _builder.AppendLine($" %{outputLabel} =w cslew {left}, {right}"); + return $"%{outputLabel}"; + } + + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.Bool)) + { + _builder.AppendLine($" %{outputLabel} =w cslew {left}, {right}"); + return $"%{outputLabel}"; + } + + break; + } + case BinaryExpressionOperator.Plus: + { + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64)) + { + _builder.AppendLine($" %{outputLabel} =l add {left}, {right}"); + return $"%{outputLabel}"; + } + + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32)) + { + _builder.AppendLine($" %{outputLabel} =w add {left}, {right}"); + return $"%{outputLabel}"; + } + + break; + } + case BinaryExpressionOperator.Minus: + { + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64)) + { + _builder.AppendLine($" %{outputLabel} =l sub {left}, {right}"); + return $"%{outputLabel}"; + } + + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32)) + { + _builder.AppendLine($" %{outputLabel} =w sub {left}, {right}"); + return $"%{outputLabel}"; + } + + break; + } + case BinaryExpressionOperator.Multiply: + { + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64)) + { + _builder.AppendLine($" %{outputLabel} =l mul {left}, {right}"); + return $"%{outputLabel}"; + } + + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32)) + { + _builder.AppendLine($" %{outputLabel} =w mul {left}, {right}"); + return $"%{outputLabel}"; + } + + break; + } + case BinaryExpressionOperator.Divide: + { + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64)) + { + _builder.AppendLine($" %{outputLabel} =l div {left}, {right}"); + return $"%{outputLabel}"; + } + + if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32)) + { + _builder.AppendLine($" %{outputLabel} =w div {left}, {right}"); + return $"%{outputLabel}"; + } + + break; + } + default: + { + throw new ArgumentOutOfRangeException(); + } + } + + throw new NotSupportedException($"Binary operator {binaryExpression.Operator} for types left: {binaryExpression.Left.Type}, right: {binaryExpression.Right.Type} not supported"); + } + + private string GenerateIdentifier(IdentifierNode identifier) + { + return _variables[identifier.Identifier].Identifier; + } + + 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 GenerateTypeConversion(string input, NubType inputType, NubType outputType) + { + if (inputType.Equals(outputType) || outputType.Equals(NubPrimitiveType.Any)) + { + return input; + } + + if (outputType is not NubPrimitiveType primitiveOutputType || inputType is not NubPrimitiveType primitiveInputType) + { + throw new NotSupportedException("Casting is only supported for primitive types"); + } + + var outputLabel = GenName("c"); + + 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($" %{outputLabel} =d sltof {input}"); + return $"%{outputLabel}"; + case PrimitiveTypeKind.F32: + _builder.AppendLine($" %{outputLabel} =s sltof {input}"); + return $"%{outputLabel}"; + case PrimitiveTypeKind.String: + 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($" %{outputLabel} =l extsw {input}"); + return $"%{outputLabel}"; + case PrimitiveTypeKind.I32: + case PrimitiveTypeKind.U32: + case PrimitiveTypeKind.I16: + case PrimitiveTypeKind.U16: + case PrimitiveTypeKind.I8: + case PrimitiveTypeKind.U8: + return input; + case PrimitiveTypeKind.F64: + var extLabel = GenName("ext"); + _builder.AppendLine($" %{extLabel} =l extsw {input}"); + _builder.AppendLine($" %{outputLabel} =d sltof {extLabel}"); + return $"%{outputLabel}"; + case PrimitiveTypeKind.F32: + _builder.AppendLine($" %{outputLabel} =s swtof {input}"); + return $"%{outputLabel}"; + case PrimitiveTypeKind.String: + case PrimitiveTypeKind.Bool: + case PrimitiveTypeKind.Any: + 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($" %{outputLabel} =l extsh {input}"); + return $"%{outputLabel}"; + case PrimitiveTypeKind.I32: + case PrimitiveTypeKind.U32: + _builder.AppendLine($" %{outputLabel} =w extsh {input}"); + return $"%{outputLabel}"; + case PrimitiveTypeKind.I16: + case PrimitiveTypeKind.U16: + case PrimitiveTypeKind.I8: + case PrimitiveTypeKind.U8: + return input; + case PrimitiveTypeKind.F64: + { + var extLabel = GenName("ext"); + _builder.AppendLine($" %{extLabel} =l extsh {input}"); + _builder.AppendLine($" %{outputLabel} =d sltof {extLabel}"); + return $"%{outputLabel}"; + } + case PrimitiveTypeKind.F32: + { + var extLabel = GenName("ext"); + _builder.AppendLine($" %{extLabel} =w extsh {input}"); + _builder.AppendLine($" %{outputLabel} =s swtof {extLabel}"); + return $"%{outputLabel}"; + } + case PrimitiveTypeKind.String: + case PrimitiveTypeKind.Bool: + case PrimitiveTypeKind.Any: + 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($" %{outputLabel} =l extsb {input}"); + return $"%{outputLabel}"; + case PrimitiveTypeKind.I32: + case PrimitiveTypeKind.U32: + case PrimitiveTypeKind.I16: + case PrimitiveTypeKind.U16: + _builder.AppendLine($" %{outputLabel} =w extsb {input}"); + return $"%{outputLabel}"; + case PrimitiveTypeKind.I8: + case PrimitiveTypeKind.U8: + return input; + case PrimitiveTypeKind.F64: + { + var extLabel = GenName("ext"); + _builder.AppendLine($" %{extLabel} =l extsb {input}"); + _builder.AppendLine($" %{outputLabel} =d sltof {extLabel}"); + return $"%{outputLabel}"; + } + case PrimitiveTypeKind.F32: + { + var extLabel = GenName("ext"); + _builder.AppendLine($" %{extLabel} =w extsb {input}"); + _builder.AppendLine($" %{outputLabel} =s swtof {extLabel}"); + return $"%{outputLabel}"; + } + case PrimitiveTypeKind.String: + case PrimitiveTypeKind.Bool: + case PrimitiveTypeKind.Any: + 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($" %{outputLabel} =d ultof {input}"); + return $"%{outputLabel}"; + case PrimitiveTypeKind.F32: + _builder.AppendLine($" %{outputLabel} =s ultof {input}"); + return $"%{outputLabel}"; + case PrimitiveTypeKind.String: + case PrimitiveTypeKind.Bool: + case PrimitiveTypeKind.Any: + 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($" %{outputLabel} =l extuw {input}"); + return $"%{outputLabel}"; + case PrimitiveTypeKind.I32: + case PrimitiveTypeKind.U32: + case PrimitiveTypeKind.I16: + case PrimitiveTypeKind.U16: + case PrimitiveTypeKind.I8: + case PrimitiveTypeKind.U8: + return input; + case PrimitiveTypeKind.F64: + var extLabel = GenName("ext"); + _builder.AppendLine($" %{extLabel} =l extuw {input}"); + _builder.AppendLine($" %{outputLabel} =d ultof {extLabel}"); + return $"%{outputLabel}"; + case PrimitiveTypeKind.F32: + _builder.AppendLine($" %{outputLabel} =s uwtof {input}"); + return $"%{outputLabel}"; + case PrimitiveTypeKind.String: + case PrimitiveTypeKind.Bool: + case PrimitiveTypeKind.Any: + 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($" %{outputLabel} =l extuh {input}"); + return $"%{outputLabel}"; + case PrimitiveTypeKind.I32: + case PrimitiveTypeKind.U32: + _builder.AppendLine($" %{outputLabel} =w extuh {input}"); + return $"%{outputLabel}"; + case PrimitiveTypeKind.I16: + case PrimitiveTypeKind.U16: + case PrimitiveTypeKind.I8: + case PrimitiveTypeKind.U8: + return input; + case PrimitiveTypeKind.F64: + { + var extLabel = GenName("ext"); + _builder.AppendLine($" %{extLabel} =l extuh {input}"); + _builder.AppendLine($" %{outputLabel} =d ultof {extLabel}"); + return $"%{outputLabel}"; + } + case PrimitiveTypeKind.F32: + { + var extLabel = GenName("ext"); + _builder.AppendLine($" %{extLabel} =w extuh {input}"); + _builder.AppendLine($" %{outputLabel} =s uwtof {extLabel}"); + return $"%{outputLabel}"; + } + case PrimitiveTypeKind.String: + case PrimitiveTypeKind.Bool: + case PrimitiveTypeKind.Any: + 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($" %{outputLabel} =l extub {input}"); + return $"%{outputLabel}"; + case PrimitiveTypeKind.I32: + case PrimitiveTypeKind.U32: + case PrimitiveTypeKind.I16: + case PrimitiveTypeKind.U16: + _builder.AppendLine($" %{outputLabel} =w extub {input}"); + return $"%{outputLabel}"; + case PrimitiveTypeKind.I8: + case PrimitiveTypeKind.U8: + return input; + case PrimitiveTypeKind.F64: + { + var extLabel = GenName("ext"); + _builder.AppendLine($" %{extLabel} =l extub {input}"); + _builder.AppendLine($" %{outputLabel} =d ultof {extLabel}"); + return $"%{outputLabel}"; + } + case PrimitiveTypeKind.F32: + { + var extLabel = GenName("ext"); + _builder.AppendLine($" %{extLabel} =w extub {input}"); + _builder.AppendLine($" %{outputLabel} =s uwtof {extLabel}"); + return $"%{outputLabel}"; + } + case PrimitiveTypeKind.String: + case PrimitiveTypeKind.Bool: + case PrimitiveTypeKind.Any: + default: + throw new NotSupportedException($"Casting from {primitiveInputType.Kind} to {primitiveOutputType.Kind} is not supported"); + } + case PrimitiveTypeKind.F64: + switch (primitiveOutputType.Kind) + { + case PrimitiveTypeKind.F64: + return input; + case PrimitiveTypeKind.F32: + _builder.AppendLine($" %{outputLabel} =s dtos {input}"); + return $"%{outputLabel}"; + 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.Bool: + case PrimitiveTypeKind.String: + case PrimitiveTypeKind.Any: + 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($" %{outputLabel} =d stord {input}"); + return $"%{outputLabel}"; + case PrimitiveTypeKind.F32: + 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.Bool: + case PrimitiveTypeKind.String: + case PrimitiveTypeKind.Any: + default: + throw new NotSupportedException($"Casting from {primitiveInputType.Kind} to {primitiveOutputType.Kind} is not supported"); + } + case PrimitiveTypeKind.Bool: + case PrimitiveTypeKind.String: + case PrimitiveTypeKind.Any: + default: + throw new NotSupportedException($"Casting from {primitiveInputType.Kind} to {primitiveOutputType.Kind} is not supported"); + } + } + + private string GenerateStructInitializer(StructInitializerNode structInitializer) + { + var structDefinition = _definitions.OfType() + .FirstOrDefault(s => s.Name == structInitializer.StructType.Name); + + if (structDefinition == null) + { + throw new Exception($"Struct {structInitializer.StructType.Name} is not defined"); + } + + var structVar = GenName("struct"); + + 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 offsetLabel = GenName("offset"); + _builder.AppendLine($" %{offsetLabel} =l add %{structVar}, {i * QbeTypeSize(field.Type)}"); + _builder.AppendLine($" store{SQT(field.Type)} {var}, %{offsetLabel}"); + } + else if (field.Value.HasValue) + { + var var = GenerateExpression(field.Value.Value); + var offsetLabel = GenName("offset"); + _builder.AppendLine($" %{offsetLabel} =l add %{structVar}, {i * QbeTypeSize(field.Type)}"); + _builder.AppendLine($" store{SQT(field.Type)} {var}, %{offsetLabel}"); + } + else + { + throw new Exception($"Field {field.Name} on struct {structInitializer.StructType.Name} is not initialized"); + } + } + + return $"%{structVar}"; + } + + private string GenerateExpressionFuncCall(FuncCallExpressionNode funcCall) + { + var outputLabel = GenName(); + var call = GenerateFuncCall(funcCall.FuncCall); + _builder.AppendLine($" %{outputLabel} ={SQT(funcCall.Type)} {call}"); + return $"%{outputLabel}"; + } + + private string GenName(string prefix = "v") + { + var index = _prefixIndexes.GetValueOrDefault(prefix, 0); + _prefixIndexes[prefix] = index + 1; + return $"{prefix}{index}"; + } + + private class Variable + { + public required string Identifier { get; init; } + public required NubType Type { get; init; } + } +} \ No newline at end of file diff --git a/lang/Nub.Lang/Frontend/Lexing/IdentifierToken.cs b/src/compiler/Nub.Lang/Frontend/Lexing/IdentifierToken.cs similarity index 100% rename from lang/Nub.Lang/Frontend/Lexing/IdentifierToken.cs rename to src/compiler/Nub.Lang/Frontend/Lexing/IdentifierToken.cs diff --git a/lang/Nub.Lang/Frontend/Lexing/Lexer.cs b/src/compiler/Nub.Lang/Frontend/Lexing/Lexer.cs similarity index 74% rename from lang/Nub.Lang/Frontend/Lexing/Lexer.cs rename to src/compiler/Nub.Lang/Frontend/Lexing/Lexer.cs index c87c255..e36c490 100644 --- a/lang/Nub.Lang/Frontend/Lexing/Lexer.cs +++ b/src/compiler/Nub.Lang/Frontend/Lexing/Lexer.cs @@ -5,9 +5,7 @@ public class Lexer private static readonly Dictionary Keywords = new() { ["func"] = Symbol.Func, - ["extern"] = Symbol.Extern, ["import"] = Symbol.Import, - ["let"] = Symbol.Let, ["if"] = Symbol.If, ["else"] = Symbol.Else, ["while"] = Symbol.While, @@ -17,6 +15,12 @@ public class Lexer ["new"] = Symbol.New, ["struct"] = Symbol.Struct, }; + + private static readonly Dictionary Modifers = new() + { + ["global"] = Modifier.Global, + ["extern"] = Modifier.Extern, + }; private static readonly Dictionary Chians = new() { @@ -84,9 +88,14 @@ public class Lexer return new SymbolToken(keywordSymbol); } + if (Modifers.TryGetValue(buffer, out var modifer)) + { + return new ModifierToken(modifer); + } + if (buffer is "true" or "false") { - return new LiteralToken(new PrimitiveType(PrimitiveTypeKind.Bool), buffer); + return new LiteralToken(NubPrimitiveType.Bool, buffer); } return new IdentifierToken(buffer); @@ -94,16 +103,41 @@ public class Lexer if (char.IsDigit(current.Value)) { + var isFloat = false; var buffer = string.Empty; - while (current.HasValue && char.IsDigit(current.Value)) + while (current.HasValue) { - buffer += current.Value; - Next(); + if (current.Value == '.') + { + if (isFloat) + { + throw new Exception("More than one period found in float literal"); + } + isFloat = true; + buffer += current.Value; + Next(); current = Peek(); + } + else if (char.IsDigit(current.Value)) + { + buffer += current.Value; + Next(); + current = Peek(); + } + else if (current.Value == 'f') + { + isFloat = true; + Next(); + break; + } + else + { + break; + } } - - return new LiteralToken(new PrimitiveType(PrimitiveTypeKind.Int64), buffer); + + return new LiteralToken(isFloat ? NubPrimitiveType.F64 : NubPrimitiveType.I64, buffer); } // TODO: Revisit this @@ -148,7 +182,7 @@ public class Lexer buffer += current.Value; } - return new LiteralToken(new StringType(), buffer); + return new LiteralToken(NubPrimitiveType.String, buffer); } if (char.IsWhiteSpace(current.Value)) diff --git a/src/compiler/Nub.Lang/Frontend/Lexing/LiteralToken.cs b/src/compiler/Nub.Lang/Frontend/Lexing/LiteralToken.cs new file mode 100644 index 0000000..a1b3b20 --- /dev/null +++ b/src/compiler/Nub.Lang/Frontend/Lexing/LiteralToken.cs @@ -0,0 +1,7 @@ +namespace Nub.Lang.Frontend.Lexing; + +public class LiteralToken(NubType type, string value) : Token +{ + public NubType Type { get; } = type; + public string Value { get; } = value; +} \ No newline at end of file diff --git a/src/compiler/Nub.Lang/Frontend/Lexing/ModifierToken.cs b/src/compiler/Nub.Lang/Frontend/Lexing/ModifierToken.cs new file mode 100644 index 0000000..3446034 --- /dev/null +++ b/src/compiler/Nub.Lang/Frontend/Lexing/ModifierToken.cs @@ -0,0 +1,12 @@ +namespace Nub.Lang.Frontend.Lexing; + +public class ModifierToken(Modifier symbol) : Token +{ + public Modifier Modifier { get; } = symbol; +} + +public enum Modifier +{ + Extern, + Global +} \ No newline at end of file diff --git a/lang/Nub.Lang/Frontend/Lexing/SymbolToken.cs b/src/compiler/Nub.Lang/Frontend/Lexing/SymbolToken.cs similarity index 96% rename from lang/Nub.Lang/Frontend/Lexing/SymbolToken.cs rename to src/compiler/Nub.Lang/Frontend/Lexing/SymbolToken.cs index 123364a..068b52f 100644 --- a/lang/Nub.Lang/Frontend/Lexing/SymbolToken.cs +++ b/src/compiler/Nub.Lang/Frontend/Lexing/SymbolToken.cs @@ -9,10 +9,8 @@ public enum Symbol { Whitespace, Import, - Extern, Func, Return, - Let, If, Else, While, diff --git a/lang/Nub.Lang/Frontend/Lexing/Token.cs b/src/compiler/Nub.Lang/Frontend/Lexing/Token.cs similarity index 100% rename from lang/Nub.Lang/Frontend/Lexing/Token.cs rename to src/compiler/Nub.Lang/Frontend/Lexing/Token.cs diff --git a/lang/Nub.Lang/Frontend/Parsing/BinaryExpressionNode.cs b/src/compiler/Nub.Lang/Frontend/Parsing/BinaryExpressionNode.cs similarity index 100% rename from lang/Nub.Lang/Frontend/Parsing/BinaryExpressionNode.cs rename to src/compiler/Nub.Lang/Frontend/Parsing/BinaryExpressionNode.cs diff --git a/lang/Nub.Lang/Frontend/Parsing/BlockNode.cs b/src/compiler/Nub.Lang/Frontend/Parsing/BlockNode.cs similarity index 100% rename from lang/Nub.Lang/Frontend/Parsing/BlockNode.cs rename to src/compiler/Nub.Lang/Frontend/Parsing/BlockNode.cs diff --git a/lang/Nub.Lang/Frontend/Parsing/BreakNode.cs b/src/compiler/Nub.Lang/Frontend/Parsing/BreakNode.cs similarity index 100% rename from lang/Nub.Lang/Frontend/Parsing/BreakNode.cs rename to src/compiler/Nub.Lang/Frontend/Parsing/BreakNode.cs diff --git a/lang/Nub.Lang/Frontend/Parsing/ContinueNode.cs b/src/compiler/Nub.Lang/Frontend/Parsing/ContinueNode.cs similarity index 100% rename from lang/Nub.Lang/Frontend/Parsing/ContinueNode.cs rename to src/compiler/Nub.Lang/Frontend/Parsing/ContinueNode.cs diff --git a/lang/Nub.Lang/Frontend/Parsing/DefinitionNode.cs b/src/compiler/Nub.Lang/Frontend/Parsing/DefinitionNode.cs similarity index 100% rename from lang/Nub.Lang/Frontend/Parsing/DefinitionNode.cs rename to src/compiler/Nub.Lang/Frontend/Parsing/DefinitionNode.cs diff --git a/lang/Nub.Lang/Frontend/Parsing/ExpressionNode.cs b/src/compiler/Nub.Lang/Frontend/Parsing/ExpressionNode.cs similarity index 81% rename from lang/Nub.Lang/Frontend/Parsing/ExpressionNode.cs rename to src/compiler/Nub.Lang/Frontend/Parsing/ExpressionNode.cs index fa7c103..c02c83c 100644 --- a/lang/Nub.Lang/Frontend/Parsing/ExpressionNode.cs +++ b/src/compiler/Nub.Lang/Frontend/Parsing/ExpressionNode.cs @@ -2,8 +2,8 @@ public abstract class ExpressionNode : Node { - private Type? _type; - public Type Type + private NubType? _type; + public NubType Type { get => _type ?? throw new Exception("Tried to access expression type before type was populated"); set => _type = value; diff --git a/lang/Nub.Lang/Frontend/Parsing/ExternFuncDefinitionNode.cs b/src/compiler/Nub.Lang/Frontend/Parsing/ExternFuncDefinitionNode.cs similarity index 74% rename from lang/Nub.Lang/Frontend/Parsing/ExternFuncDefinitionNode.cs rename to src/compiler/Nub.Lang/Frontend/Parsing/ExternFuncDefinitionNode.cs index 466b903..f841795 100644 --- a/lang/Nub.Lang/Frontend/Parsing/ExternFuncDefinitionNode.cs +++ b/src/compiler/Nub.Lang/Frontend/Parsing/ExternFuncDefinitionNode.cs @@ -1,10 +1,10 @@ namespace Nub.Lang.Frontend.Parsing; -public class ExternFuncDefinitionNode(string name, List parameters, Optional returnType) : DefinitionNode +public class ExternFuncDefinitionNode(string name, List parameters, Optional returnType) : DefinitionNode { public string Name { get; } = name; public List Parameters { get; } = parameters; - public Optional ReturnType { get; } = returnType; + public Optional ReturnType { get; } = returnType; public override string ToString() => $"{Name}({string.Join(", ", Parameters.Select(p => p.ToString()))}){(ReturnType.HasValue ? ": " + ReturnType.Value : "")}"; } \ No newline at end of file diff --git a/lang/Nub.Lang/Frontend/Parsing/FuncCall.cs b/src/compiler/Nub.Lang/Frontend/Parsing/FuncCall.cs similarity index 100% rename from lang/Nub.Lang/Frontend/Parsing/FuncCall.cs rename to src/compiler/Nub.Lang/Frontend/Parsing/FuncCall.cs diff --git a/lang/Nub.Lang/Frontend/Parsing/FuncCallExpressionNode.cs b/src/compiler/Nub.Lang/Frontend/Parsing/FuncCallExpressionNode.cs similarity index 100% rename from lang/Nub.Lang/Frontend/Parsing/FuncCallExpressionNode.cs rename to src/compiler/Nub.Lang/Frontend/Parsing/FuncCallExpressionNode.cs diff --git a/lang/Nub.Lang/Frontend/Parsing/FuncCallStatementNode.cs b/src/compiler/Nub.Lang/Frontend/Parsing/FuncCallStatementNode.cs similarity index 100% rename from lang/Nub.Lang/Frontend/Parsing/FuncCallStatementNode.cs rename to src/compiler/Nub.Lang/Frontend/Parsing/FuncCallStatementNode.cs diff --git a/lang/Nub.Lang/Frontend/Parsing/IdentifierNode.cs b/src/compiler/Nub.Lang/Frontend/Parsing/IdentifierNode.cs similarity index 100% rename from lang/Nub.Lang/Frontend/Parsing/IdentifierNode.cs rename to src/compiler/Nub.Lang/Frontend/Parsing/IdentifierNode.cs diff --git a/lang/Nub.Lang/Frontend/Parsing/IfNode.cs b/src/compiler/Nub.Lang/Frontend/Parsing/IfNode.cs similarity index 100% rename from lang/Nub.Lang/Frontend/Parsing/IfNode.cs rename to src/compiler/Nub.Lang/Frontend/Parsing/IfNode.cs diff --git a/src/compiler/Nub.Lang/Frontend/Parsing/LiteralNode.cs b/src/compiler/Nub.Lang/Frontend/Parsing/LiteralNode.cs new file mode 100644 index 0000000..b9798bc --- /dev/null +++ b/src/compiler/Nub.Lang/Frontend/Parsing/LiteralNode.cs @@ -0,0 +1,7 @@ +namespace Nub.Lang.Frontend.Parsing; + +public class LiteralNode(string literal, NubType type) : ExpressionNode +{ + public string Literal { get; } = literal; + public NubType LiteralType { get; } = type; +} \ No newline at end of file diff --git a/lang/Nub.Lang/Frontend/Parsing/LocalFuncDefinitionNode.cs b/src/compiler/Nub.Lang/Frontend/Parsing/LocalFuncDefinitionNode.cs similarity index 67% rename from lang/Nub.Lang/Frontend/Parsing/LocalFuncDefinitionNode.cs rename to src/compiler/Nub.Lang/Frontend/Parsing/LocalFuncDefinitionNode.cs index 561b40d..6734a02 100644 --- a/lang/Nub.Lang/Frontend/Parsing/LocalFuncDefinitionNode.cs +++ b/src/compiler/Nub.Lang/Frontend/Parsing/LocalFuncDefinitionNode.cs @@ -1,11 +1,12 @@ namespace Nub.Lang.Frontend.Parsing; -public class LocalFuncDefinitionNode(string name, List parameters, BlockNode body, Optional returnType) : DefinitionNode +public class LocalFuncDefinitionNode(string name, List parameters, BlockNode body, Optional returnType, bool global) : DefinitionNode { public string Name { get; } = name; public List Parameters { get; } = parameters; public BlockNode Body { get; } = body; - public Optional ReturnType { get; } = returnType; + public Optional ReturnType { get; } = returnType; + public bool Global { get; } = global; public override string ToString() => $"{Name}({string.Join(", ", Parameters.Select(p => p.ToString()))}){(ReturnType.HasValue ? ": " + ReturnType.Value : "")}"; } \ No newline at end of file diff --git a/lang/Nub.Lang/Frontend/Parsing/ModuleNode.cs b/src/compiler/Nub.Lang/Frontend/Parsing/ModuleNode.cs similarity index 100% rename from lang/Nub.Lang/Frontend/Parsing/ModuleNode.cs rename to src/compiler/Nub.Lang/Frontend/Parsing/ModuleNode.cs diff --git a/lang/Nub.Lang/Frontend/Parsing/Node.cs b/src/compiler/Nub.Lang/Frontend/Parsing/Node.cs similarity index 100% rename from lang/Nub.Lang/Frontend/Parsing/Node.cs rename to src/compiler/Nub.Lang/Frontend/Parsing/Node.cs diff --git a/lang/Nub.Lang/Frontend/Parsing/Parser.cs b/src/compiler/Nub.Lang/Frontend/Parsing/Parser.cs similarity index 65% rename from lang/Nub.Lang/Frontend/Parsing/Parser.cs rename to src/compiler/Nub.Lang/Frontend/Parsing/Parser.cs index 8a4d508..952efed 100644 --- a/lang/Nub.Lang/Frontend/Parsing/Parser.cs +++ b/src/compiler/Nub.Lang/Frontend/Parsing/Parser.cs @@ -7,26 +7,20 @@ public class Parser { private List _tokens = []; private int _index; - + public ModuleNode ParseModule(List tokens, string path) { _index = 0; _tokens = tokens; - + List definitions = []; List imports = []; - + while (Peek().HasValue) { if (TryExpectSymbol(Symbol.Import)) { - var name = ExpectLiteral(); - if (name.Type is not StringType) - { - throw new Exception("Import statements must have a string literal value"); - } - - TryExpectSymbol(Symbol.Semicolon); + var name = ExpectIdentifier(); imports.Add(name.Value); } else @@ -40,28 +34,23 @@ public class Parser private DefinitionNode ParseDefinition() { + List modifiers = []; + + while (TryExpectModifier(out var modifier)) + { + modifiers.Add(modifier); + } + var keyword = ExpectSymbol(); return keyword.Symbol switch { - Symbol.Let => ParseGlobalVariableDefinition(), - Symbol.Func => ParseFuncDefinition(), - Symbol.Extern => ParseExternFuncDefinition(), - Symbol.Struct => ParseStruct(), + Symbol.Func => ParseFuncDefinition(modifiers), + Symbol.Struct => ParseStruct(modifiers), _ => throw new Exception("Unexpected symbol: " + keyword.Symbol) }; } - private GlobalVariableDefinitionNode ParseGlobalVariableDefinition() - { - var name = ExpectIdentifier(); - ExpectSymbol(Symbol.Assign); - var value = ParseExpression(); - ExpectSymbol(Symbol.Semicolon); - - return new GlobalVariableDefinitionNode(name.Value, value); - } - - private LocalFuncDefinitionNode ParseFuncDefinition() + private DefinitionNode ParseFuncDefinition(List modifiers) { var name = ExpectIdentifier(); List parameters = []; @@ -75,80 +64,73 @@ public class Parser } } - var returnType = Optional.Empty(); + var returnType = Optional.Empty(); if (TryExpectSymbol(Symbol.Colon)) { returnType = ParseType(); } - var body = ParseBlock(); - - return new LocalFuncDefinitionNode(name.Value, parameters, body, returnType); - } - - private ExternFuncDefinitionNode ParseExternFuncDefinition() - { - ExpectSymbol(Symbol.Func); - var name = ExpectIdentifier(); - List parameters = []; - ExpectSymbol(Symbol.OpenParen); - if (!TryExpectSymbol(Symbol.CloseParen)) + if (modifiers.Remove(Modifier.Extern)) { - while (!TryExpectSymbol(Symbol.CloseParen)) + if (modifiers.Count != 0) { - parameters.Add(ParseFuncParameter()); - TryExpectSymbol(Symbol.Comma); + throw new Exception($"Modifiers: {string.Join(", ", modifiers)} is not valid for an extern function"); } + return new ExternFuncDefinitionNode(name.Value, parameters, returnType); } - - var returnType = Optional.Empty(); - if (TryExpectSymbol(Symbol.Colon)) - { - returnType = ParseType(); - } - - ExpectSymbol(Symbol.Semicolon); - return new ExternFuncDefinitionNode(name.Value, parameters, returnType); + var body = ParseBlock(); + var global = modifiers.Remove(Modifier.Global); + + if (modifiers.Count != 0) + { + throw new Exception($"Modifiers: {string.Join(", ", modifiers)} is not valid for a local function"); + } + return new LocalFuncDefinitionNode(name.Value, parameters, body, returnType, global); } - private StructDefinitionNode ParseStruct() + private StructDefinitionNode ParseStruct(List modifiers) { var name = ExpectIdentifier().Value; ExpectSymbol(Symbol.OpenBrace); - - List variables = []; - + + List variables = []; + while (!TryExpectSymbol(Symbol.CloseBrace)) { - ExpectSymbol(Symbol.Let); var variableName = ExpectIdentifier().Value; ExpectSymbol(Symbol.Colon); var variableType = ParseType(); var variableValue = Optional.Empty(); - + if (TryExpectSymbol(Symbol.Assign)) { variableValue = ParseExpression(); } - - ExpectSymbol(Symbol.Semicolon); - - variables.Add(new StructMember(variableName, variableType, variableValue)); + + variables.Add(new StructField(variableName, variableType, variableValue)); } - + return new StructDefinitionNode(name, variables); } private FuncParameter ParseFuncParameter() { + var variadic = false; + if (TryExpectSymbol(Symbol.Period)) + { + ExpectSymbol(Symbol.Period); + ExpectSymbol(Symbol.Period); + variadic = true; + } + var name = ExpectIdentifier(); ExpectSymbol(Symbol.Colon); var type = ParseType(); - return new FuncParameter(name.Value, type); + return new FuncParameter(name.Value, type, variadic); } private StatementNode ParseStatement() @@ -170,29 +152,19 @@ public class Parser TryExpectSymbol(Symbol.Comma); } - ExpectSymbol(Symbol.Semicolon); - - if (identifier.Value == "syscall") - { - return new SyscallStatementNode(new Syscall(parameters)); - } - return new FuncCallStatementNode(new FuncCall(identifier.Value, parameters)); } - case Symbol.OpenBracket: - { - var index = ParseExpression(); - ExpectSymbol(Symbol.CloseBracket); - ExpectSymbol(Symbol.Assign); - var value = ParseExpression(); - ExpectSymbol(Symbol.Semicolon); - return new ArrayIndexAssignmentNode(new IdentifierNode(identifier.Value), index, value); - } case Symbol.Assign: { var value = ParseExpression(); - ExpectSymbol(Symbol.Semicolon); - return new VariableReassignmentNode(identifier.Value, value); + return new VariableAssignmentNode(identifier.Value, Optional.Empty(), value); + } + case Symbol.Colon: + { + var type = ParseType(); + ExpectSymbol(Symbol.Assign); + var value = ParseExpression(); + return new VariableAssignmentNode(identifier.Value,type, value); } default: { @@ -205,11 +177,10 @@ public class Parser return symbol.Symbol switch { Symbol.Return => ParseReturn(), - Symbol.Let => ParseVariableAssignment(), Symbol.If => ParseIf(), Symbol.While => ParseWhile(), - Symbol.Break => ParseBreak(), - Symbol.Continue => ParseContinue(), + Symbol.Break => new BreakNode(), + Symbol.Continue => new ContinueNode(), _ => throw new Exception($"Unexpected symbol {symbol.Symbol}") }; } @@ -226,22 +197,11 @@ public class Parser if (!TryExpectSymbol(Symbol.Semicolon)) { value = ParseExpression(); - ExpectSymbol(Symbol.Semicolon); } return new ReturnNode(value); } - private VariableAssignmentNode ParseVariableAssignment() - { - var name = ExpectIdentifier().Value; - ExpectSymbol(Symbol.Assign); - var value = ParseExpression(); - ExpectSymbol(Symbol.Semicolon); - - return new VariableAssignmentNode(name, value); - } - private IfNode ParseIf() { var condition = ParseExpression(); @@ -254,7 +214,7 @@ public class Parser ? (Variant)ParseIf() : (Variant)ParseBlock(); } - + return new IfNode(condition, body, elseStatement); } @@ -265,18 +225,6 @@ public class Parser return new WhileNode(condition, body); } - private BreakNode ParseBreak() - { - ExpectSymbol(Symbol.Semicolon); - return new BreakNode(); - } - - private ContinueNode ParseContinue() - { - ExpectSymbol(Symbol.Semicolon); - return new ContinueNode(); - } - private ExpressionNode ParseExpression(int precedence = 0) { var left = ParsePrimaryExpression(); @@ -284,15 +232,16 @@ public class Parser while (true) { var token = Peek(); - if (!token.HasValue || token.Value is not SymbolToken symbolToken || !TryGetBinaryOperator(symbolToken.Symbol, out var op) || GetBinaryOperatorPrecedence(op.Value) < precedence) + if (!token.HasValue || token.Value is not SymbolToken symbolToken || !TryGetBinaryOperator(symbolToken.Symbol, out var op) || + GetBinaryOperatorPrecedence(op.Value) < precedence) break; - + Next(); var right = ParseExpression(GetBinaryOperatorPrecedence(op.Value) + 1); left = new BinaryExpressionNode(left, op.Value, right); } - + return left; } @@ -360,9 +309,13 @@ public class Parser switch (token) { case LiteralToken literal: + { return new LiteralNode(literal.Value, literal.Type); + } case IdentifierToken identifier: + { return ParseExpressionIdentifier(identifier); + } case SymbolToken symbolToken: { switch (symbolToken.Symbol) @@ -376,44 +329,25 @@ public class Parser case Symbol.New: { var type = ParseType(); - switch (type) + Dictionary initializers = []; + ExpectSymbol(Symbol.OpenBrace); + while (!TryExpectSymbol(Symbol.CloseBrace)) { - case ArrayType: - { - ExpectSymbol(Symbol.OpenParen); - var size = ExpectLiteral(); - if (size.Type is not PrimitiveType { Kind: PrimitiveTypeKind.Int64 }) - { - throw new Exception($"Array initializer size must be an {PrimitiveTypeKind.Int64}"); - } - ExpectSymbol(Symbol.CloseParen); - - return new ArrayInitializerNode(long.Parse(size.Value), type); - } - case StructType structType: - { - Dictionary initializers = []; - ExpectSymbol(Symbol.OpenBrace); - while (!TryExpectSymbol(Symbol.CloseBrace)) - { - var name = ExpectIdentifier().Value; - ExpectSymbol(Symbol.Assign); - var value = ParseExpression(); - TryExpectSymbol(Symbol.Comma); - initializers.Add(name, value); - } - - return new StructInitializerNode(structType, initializers); - } - default: - throw new Exception($"Type {type} cannot be initialized with the new keyword"); + var name = ExpectIdentifier().Value; + ExpectSymbol(Symbol.Assign); + var value = ParseExpression(); + initializers.Add(name, value); } + + return new StructInitializerNode(type, initializers); } default: + { throw new Exception($"Unknown symbol: {symbolToken.Symbol}"); + } } } - default: + default: throw new Exception($"Unexpected token type {token.GetType().Name}"); } } @@ -435,25 +369,15 @@ public class Parser case Symbol.Period: { Next(); - List members = - [ - identifier.Value, - ExpectIdentifier().Value - ]; + ExpressionNode result = new IdentifierNode(identifier.Value); - while (TryExpectSymbol(Symbol.Period)) + do { - members.Add(ExpectIdentifier().Value); - } + var field = ExpectIdentifier(); + result = new StructFieldAccessorNode(result, field.Value); + } while (TryExpectSymbol(Symbol.Period)); - return new StructMemberAccessorNode(members); - } - case Symbol.OpenBracket: - { - Next(); - var index = ParseExpression(); - ExpectSymbol(Symbol.CloseBracket); - return new ArrayIndexAccessNode(new IdentifierNode(identifier.Value), index); + return result; } case Symbol.OpenParen: { @@ -465,18 +389,14 @@ public class Parser TryExpectSymbol(Symbol.Comma); } - if (identifier.Value == "syscall") - { - return new SyscallExpressionNode(new Syscall(parameters)); - } - return new FuncCallExpressionNode(new FuncCall(identifier.Value, parameters)); } } + break; } } - + return new IdentifierNode(identifier.Value); } @@ -492,36 +412,10 @@ public class Parser return new BlockNode(statements); } - private Type ParseType() + private NubType ParseType() { var name = ExpectIdentifier().Value; - switch (name) - { - case "String": - { - return new StringType(); - } - case "Array": - { - ExpectSymbol(Symbol.LessThan); - var innerType = ParseType(); - ExpectSymbol(Symbol.GreaterThan); - return new ArrayType(innerType); - } - case "Any": - { - return new AnyType(); - } - default: - { - if (PrimitiveType.TryParse(name, out var primitiveType)) - { - return primitiveType; - } - - return new StructType(name); - } - } + return NubType.Parse(name); } private Token ExpectToken() @@ -563,6 +457,19 @@ public class Parser return result; } + private bool TryExpectModifier(out Modifier modifier) + { + if (Peek() is { HasValue: true, Value: ModifierToken modifierToken }) + { + modifier = modifierToken.Modifier; + Next(); + return true; + } + + modifier = default; + return false; + } + private IdentifierToken ExpectIdentifier() { var token = ExpectToken(); diff --git a/lang/Nub.Lang/Frontend/Parsing/ReturnNode.cs b/src/compiler/Nub.Lang/Frontend/Parsing/ReturnNode.cs similarity index 100% rename from lang/Nub.Lang/Frontend/Parsing/ReturnNode.cs rename to src/compiler/Nub.Lang/Frontend/Parsing/ReturnNode.cs diff --git a/lang/Nub.Lang/Frontend/Parsing/StatementNode.cs b/src/compiler/Nub.Lang/Frontend/Parsing/StatementNode.cs similarity index 100% rename from lang/Nub.Lang/Frontend/Parsing/StatementNode.cs rename to src/compiler/Nub.Lang/Frontend/Parsing/StatementNode.cs diff --git a/src/compiler/Nub.Lang/Frontend/Parsing/StructDefinitionNode.cs b/src/compiler/Nub.Lang/Frontend/Parsing/StructDefinitionNode.cs new file mode 100644 index 0000000..dca6eab --- /dev/null +++ b/src/compiler/Nub.Lang/Frontend/Parsing/StructDefinitionNode.cs @@ -0,0 +1,7 @@ +namespace Nub.Lang.Frontend.Parsing; + +public class StructDefinitionNode(string name, List fields) : DefinitionNode +{ + public string Name { get; } = name; + public List Fields { get; } = fields; +} \ No newline at end of file diff --git a/src/compiler/Nub.Lang/Frontend/Parsing/StructFieldAccessorNode.cs b/src/compiler/Nub.Lang/Frontend/Parsing/StructFieldAccessorNode.cs new file mode 100644 index 0000000..cd7e285 --- /dev/null +++ b/src/compiler/Nub.Lang/Frontend/Parsing/StructFieldAccessorNode.cs @@ -0,0 +1,7 @@ +namespace Nub.Lang.Frontend.Parsing; + +public class StructFieldAccessorNode(ExpressionNode @struct, string field) : ExpressionNode +{ + public ExpressionNode Struct { get; } = @struct; + public string Field { get; } = field; +} \ No newline at end of file diff --git a/src/compiler/Nub.Lang/Frontend/Parsing/StructInitializerNode.cs b/src/compiler/Nub.Lang/Frontend/Parsing/StructInitializerNode.cs new file mode 100644 index 0000000..2ab9b85 --- /dev/null +++ b/src/compiler/Nub.Lang/Frontend/Parsing/StructInitializerNode.cs @@ -0,0 +1,7 @@ +namespace Nub.Lang.Frontend.Parsing; + +public class StructInitializerNode(NubType structType, Dictionary initializers) : ExpressionNode +{ + public NubType StructType { get; } = structType; + public Dictionary Initializers { get; } = initializers; +} \ No newline at end of file diff --git a/src/compiler/Nub.Lang/Frontend/Parsing/VariableAssignmentNode.cs b/src/compiler/Nub.Lang/Frontend/Parsing/VariableAssignmentNode.cs new file mode 100644 index 0000000..2964d04 --- /dev/null +++ b/src/compiler/Nub.Lang/Frontend/Parsing/VariableAssignmentNode.cs @@ -0,0 +1,8 @@ +namespace Nub.Lang.Frontend.Parsing; + +public class VariableAssignmentNode(string name, Optional explicitType, ExpressionNode value) : StatementNode +{ + public string Name { get; } = name; + public Optional ExplicitType { get; } = explicitType; + public ExpressionNode Value { get; } = value; +} \ No newline at end of file diff --git a/lang/Nub.Lang/Frontend/Parsing/WhileNode.cs b/src/compiler/Nub.Lang/Frontend/Parsing/WhileNode.cs similarity index 100% rename from lang/Nub.Lang/Frontend/Parsing/WhileNode.cs rename to src/compiler/Nub.Lang/Frontend/Parsing/WhileNode.cs diff --git a/lang/Nub.Lang/Frontend/Typing/ExpressionTyper.cs b/src/compiler/Nub.Lang/Frontend/Typing/ExpressionTyper.cs similarity index 59% rename from lang/Nub.Lang/Frontend/Typing/ExpressionTyper.cs rename to src/compiler/Nub.Lang/Frontend/Typing/ExpressionTyper.cs index 4bae6cf..09fb4b3 100644 --- a/lang/Nub.Lang/Frontend/Typing/ExpressionTyper.cs +++ b/src/compiler/Nub.Lang/Frontend/Typing/ExpressionTyper.cs @@ -2,18 +2,17 @@ namespace Nub.Lang.Frontend.Typing; -public class Func(string name, List parameters, Optional body, Optional returnType) +public class Func(string name, List parameters, Optional body, Optional returnType) { public string Name { get; } = name; public List Parameters { get; } = parameters; public Optional Body { get; } = body; - public Optional ReturnType { get; } = returnType; + public Optional ReturnType { get; } = returnType; } public class ExpressionTyper { private readonly List _functions; - private readonly List _variableDefinitions; private readonly List _structDefinitions; private readonly Stack _variables; @@ -21,7 +20,6 @@ public class ExpressionTyper { _variables = new Stack(); _functions = []; - _variableDefinitions = []; _structDefinitions = definitions.OfType().ToList(); @@ -37,7 +35,6 @@ public class ExpressionTyper _functions.AddRange(functions); _functions.AddRange(externFunctions); - _variableDefinitions.AddRange(definitions.OfType()); } public void Populate() @@ -46,7 +43,7 @@ public class ExpressionTyper foreach (var @class in _structDefinitions) { - foreach (var variable in @class.Members) + foreach (var variable in @class.Fields) { if (variable.Value.HasValue) { @@ -55,12 +52,6 @@ public class ExpressionTyper } } - foreach (var variable in _variableDefinitions) - { - PopulateExpression(variable.Value); - _variables.Push(new Variable(variable.Name, variable.Value.Type)); - } - foreach (var function in _functions) { foreach (var parameter in function.Parameters) @@ -96,9 +87,6 @@ public class ExpressionTyper { switch (statement) { - case ArrayIndexAssignmentNode arrayIndexAssignment: - PopulateArrayIndexAssignment(arrayIndexAssignment); - break; case BreakNode: case ContinueNode: break; @@ -111,15 +99,9 @@ public class ExpressionTyper case ReturnNode returnNode: PopulateReturn(returnNode); break; - case SyscallStatementNode syscall: - PopulateSyscallStatement(syscall); - break; case VariableAssignmentNode variableAssignment: PopulateVariableAssignment(variableAssignment); break; - case VariableReassignmentNode variableReassignment: - PopulateVariableReassignment(variableReassignment); - break; case WhileNode whileStatement: PopulateWhileStatement(whileStatement); break; @@ -128,13 +110,6 @@ public class ExpressionTyper } } - private void PopulateArrayIndexAssignment(ArrayIndexAssignmentNode arrayIndexAssignment) - { - PopulateIdentifier(arrayIndexAssignment.Identifier); - PopulateExpression(arrayIndexAssignment.Index); - PopulateExpression(arrayIndexAssignment.Value); - } - private void PopulateFuncCallStatement(FuncCallStatementNode funcCall) { foreach (var parameter in funcCall.FuncCall.Parameters) @@ -157,14 +132,6 @@ public class ExpressionTyper } } - private void PopulateSyscallStatement(SyscallStatementNode syscall) - { - foreach (var parameter in syscall.Syscall.Parameters) - { - PopulateExpression(parameter); - } - } - private void PopulateReturn(ReturnNode returnNode) { if (returnNode.Value.HasValue) @@ -176,12 +143,12 @@ public class ExpressionTyper private void PopulateVariableAssignment(VariableAssignmentNode variableAssignment) { PopulateExpression(variableAssignment.Value); - _variables.Push(new Variable(variableAssignment.Name, variableAssignment.Value.Type)); + _variables.Push(new Variable(variableAssignment.Name, variableAssignment.ExplicitType.HasValue ? variableAssignment.ExplicitType.Value : variableAssignment.Value.Type)); } - private void PopulateVariableReassignment(VariableReassignmentNode variableReassignment) + private void PopulateVariableReassignment(VariableAssignmentNode variableAssignment) { - PopulateExpression(variableReassignment.Value); + PopulateExpression(variableAssignment.Value); } private void PopulateWhileStatement(WhileNode whileStatement) @@ -194,12 +161,6 @@ public class ExpressionTyper { switch (expression) { - case ArrayIndexAccessNode arrayIndexAccess: - PopulateArrayIndexAccess(arrayIndexAccess); - break; - case ArrayInitializerNode arrayInitializer: - PopulateArrayInitializer(arrayInitializer); - break; case BinaryExpressionNode binaryExpression: PopulateBinaryExpression(binaryExpression); break; @@ -215,41 +176,14 @@ public class ExpressionTyper case StructInitializerNode structInitializer: PopulateStructInitializer(structInitializer); break; - case StructMemberAccessorNode structMemberAccessor: - GenerateStructMemberAccessorNode(structMemberAccessor); - break; - case SyscallExpressionNode syscall: - PopulateSyscallExpression(syscall); + case StructFieldAccessorNode structMemberAccessor: + PopulateStructMemberAccessorNode(structMemberAccessor); break; default: throw new ArgumentOutOfRangeException(nameof(expression)); } } - private void PopulateArrayIndexAccess(ArrayIndexAccessNode arrayIndexAccess) - { - PopulateExpression(arrayIndexAccess.Index); - PopulateIdentifier(arrayIndexAccess.Identifier); - - var variable = _variables.FirstOrDefault(v => v.Name == arrayIndexAccess.Identifier.Identifier); - if (variable == null) - { - throw new Exception($"Variable {arrayIndexAccess.Identifier} is not defined"); - } - - if (variable.Type is not ArrayType arrayType) - { - throw new Exception($"Variable {arrayIndexAccess.Identifier} is not an array type"); - } - - arrayIndexAccess.Type = arrayType.InnerType; - } - - private void PopulateArrayInitializer(ArrayInitializerNode arrayInitializer) - { - arrayInitializer.Type = arrayInitializer.InnerType; - } - private void PopulateBinaryExpression(BinaryExpressionNode binaryExpression) { PopulateExpression(binaryExpression.Left); @@ -263,7 +197,7 @@ public class ExpressionTyper case BinaryExpressionOperator.LessThan: case BinaryExpressionOperator.LessThanOrEqual: { - binaryExpression.Type = new PrimitiveType(PrimitiveTypeKind.Bool); + binaryExpression.Type = NubPrimitiveType.Bool; break; } case BinaryExpressionOperator.Plus: @@ -325,68 +259,34 @@ public class ExpressionTyper structInitializer.Type = structInitializer.StructType; } - // TODO: Fix this ugly ass code - private void GenerateStructMemberAccessorNode(StructMemberAccessorNode structMemberAccessor) + private void PopulateStructMemberAccessorNode(StructFieldAccessorNode structFieldAccessor) { - var variable = _variables.FirstOrDefault(v => v.Name == structMemberAccessor.Members[0]); - if (variable == null) + PopulateExpression(structFieldAccessor.Struct); + + var structType = structFieldAccessor.Struct.Type; + if (structType == null) { - throw new Exception($"Variable {structMemberAccessor.Members[0]} is not defined"); + throw new Exception($"Cannot access field on non-struct type: {structFieldAccessor.Struct}"); } - - if (variable.Type is not StructType variableType) + + var structDefinition = _structDefinitions.FirstOrDefault(s => s.Name == structType.Name); + if (structDefinition == null) { - throw new Exception("Variable " + structMemberAccessor.Members[0] + " is not a struct"); + throw new Exception($"Struct {structType.Name} is not defined"); } - - var definition = _structDefinitions.FirstOrDefault(sd => sd.Name == variableType.Name); - if (definition == null) + + var field = structDefinition.Fields.FirstOrDefault(f => f.Name == structFieldAccessor.Field); + if (field == null) { - throw new Exception($"Struct {structMemberAccessor.Members[0]} is not defined"); + throw new Exception($"Field {structFieldAccessor.Field} is not defined in struct {structType.Name}"); } - - for (var i = 1; i < structMemberAccessor.Members.Count - 1; i++) - { - var member = definition.Members.FirstOrDefault(m => m.Name == structMemberAccessor.Members[i]); - if (member == null) - { - throw new Exception($"Member {structMemberAccessor.Members[i]} does not exist on struct {definition.Name}"); - } - - if (member.Type is not StructType memberType) - { - throw new Exception($"Member {structMemberAccessor.Members[i]} on struct {definition.Name} is not a struct"); - } - - definition = _structDefinitions.FirstOrDefault(sd => sd.Name == memberType.Name); - if (definition == null) - { - throw new Exception($"Struct {structMemberAccessor.Members[i]} is not defined"); - } - } - - var tmp = definition.Members.FirstOrDefault(m => m.Name == structMemberAccessor.Members.Last()); - if (tmp == null) - { - throw new Exception($"Member {structMemberAccessor.Members.Last()} does not exist on struct {definition.Name}"); - } - - structMemberAccessor.Type = tmp.Type; + + structFieldAccessor.Type = field.Type; } - private void PopulateSyscallExpression(SyscallExpressionNode syscall) - { - foreach (var parameter in syscall.Syscall.Parameters) - { - PopulateExpression(parameter); - } - - syscall.Type = new PrimitiveType(PrimitiveTypeKind.Int64); - } - - private class Variable(string name, Type type) + private class Variable(string name, NubType type) { public string Name { get; } = name; - public Type Type { get; } = type; + public NubType Type { get; } = type; } } \ No newline at end of file diff --git a/src/compiler/Nub.Lang/FuncParameter.cs b/src/compiler/Nub.Lang/FuncParameter.cs new file mode 100644 index 0000000..6f19992 --- /dev/null +++ b/src/compiler/Nub.Lang/FuncParameter.cs @@ -0,0 +1,10 @@ +namespace Nub.Lang; + +public class FuncParameter(string name, NubType type, bool variadic) +{ + public string Name { get; } = name; + public NubType Type { get; } = type; + public bool Variadic { get; } = variadic; + + public override string ToString() => $"{Name}: {Type}"; +} \ No newline at end of file diff --git a/lang/Nub.Lang/Nub.Lang.csproj b/src/compiler/Nub.Lang/Nub.Lang.csproj similarity index 76% rename from lang/Nub.Lang/Nub.Lang.csproj rename to src/compiler/Nub.Lang/Nub.Lang.csproj index 85b4959..ee51912 100644 --- a/lang/Nub.Lang/Nub.Lang.csproj +++ b/src/compiler/Nub.Lang/Nub.Lang.csproj @@ -1,10 +1,12 @@  + nub Exe net9.0 enable enable + true diff --git a/src/compiler/Nub.Lang/NubType.cs b/src/compiler/Nub.Lang/NubType.cs new file mode 100644 index 0000000..c69f582 --- /dev/null +++ b/src/compiler/Nub.Lang/NubType.cs @@ -0,0 +1,120 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Nub.Lang; + +public abstract class NubType +{ + protected NubType(string name) + { + Name = name; + } + + public string Name { get; } + + public static NubType Parse(string s) + { + if (NubPrimitiveType.TryParse(s, out var kind)) + { + return new NubPrimitiveType(kind.Value); + } + + return new NubCustomType(s); + } + + public override bool Equals(object? obj) => obj is NubType item && Name.Equals(item.Name); + public override int GetHashCode() => HashCode.Combine(Name); + public override string ToString() => Name; +} + +public class NubCustomType(string name) : NubType(name); + +public class NubPrimitiveType : NubType +{ + public NubPrimitiveType(PrimitiveTypeKind kind) : base(KindToString(kind)) + { + Kind = kind; + } + + public PrimitiveTypeKind Kind { get; } + + public static NubPrimitiveType I64 => new(PrimitiveTypeKind.I64); + public static NubPrimitiveType I32 => new(PrimitiveTypeKind.I32); + public static NubPrimitiveType I16 => new(PrimitiveTypeKind.I16); + public static NubPrimitiveType I8 => new(PrimitiveTypeKind.I8); + + public static NubPrimitiveType U64 => new(PrimitiveTypeKind.U64); + public static NubPrimitiveType U32 => new(PrimitiveTypeKind.U32); + public static NubPrimitiveType U16 => new(PrimitiveTypeKind.U16); + public static NubPrimitiveType U8 => new(PrimitiveTypeKind.U8); + + public static NubPrimitiveType F64 => new(PrimitiveTypeKind.F64); + public static NubPrimitiveType F32 => new(PrimitiveTypeKind.F32); + + public static NubPrimitiveType Bool => new(PrimitiveTypeKind.Bool); + public static NubPrimitiveType String => new(PrimitiveTypeKind.String); + public static NubPrimitiveType Any => new(PrimitiveTypeKind.Any); + + public static bool TryParse(string s, [NotNullWhen(true)] out PrimitiveTypeKind? kind) + { + kind = s switch + { + "i64" => PrimitiveTypeKind.I64, + "i32" => PrimitiveTypeKind.I32, + "i16" => PrimitiveTypeKind.I16, + "i8" => PrimitiveTypeKind.I8, + "u64" => PrimitiveTypeKind.U64, + "u32" => PrimitiveTypeKind.U32, + "u16" => PrimitiveTypeKind.U16, + "u8" => PrimitiveTypeKind.U8, + "f64" => PrimitiveTypeKind.F64, + "f32" => PrimitiveTypeKind.F32, + "bool" => PrimitiveTypeKind.Bool, + "string" => PrimitiveTypeKind.String, + "any" => PrimitiveTypeKind.Any, + _ => null + }; + + return kind != null; + } + + public static string KindToString(PrimitiveTypeKind kind) + { + return kind switch + { + PrimitiveTypeKind.I8 => "i8", + PrimitiveTypeKind.I16 => "i16", + PrimitiveTypeKind.I32 => "i32", + PrimitiveTypeKind.I64 => "i64", + + PrimitiveTypeKind.U8 => "u8", + PrimitiveTypeKind.U16 => "u16", + PrimitiveTypeKind.U32 => "u32", + PrimitiveTypeKind.U64 => "u64", + + PrimitiveTypeKind.F32 => "f32", + PrimitiveTypeKind.F64 => "f64", + + PrimitiveTypeKind.Bool => "bool", + PrimitiveTypeKind.String => "string", + PrimitiveTypeKind.Any => "any", + _ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null) + }; + } +} + +public enum PrimitiveTypeKind +{ + I64, + I32, + I16, + I8, + U64, + U32, + U16, + U8, + F64, + F32, + Bool, + String, + Any +} \ No newline at end of file diff --git a/lang/Nub.Lang/Optional.cs b/src/compiler/Nub.Lang/Optional.cs similarity index 100% rename from lang/Nub.Lang/Optional.cs rename to src/compiler/Nub.Lang/Optional.cs diff --git a/lang/Nub.Lang/Program.cs b/src/compiler/Nub.Lang/Program.cs similarity index 95% rename from lang/Nub.Lang/Program.cs rename to src/compiler/Nub.Lang/Program.cs index 33da40d..955028a 100644 --- a/lang/Nub.Lang/Program.cs +++ b/src/compiler/Nub.Lang/Program.cs @@ -1,4 +1,4 @@ -using Nub.Lang.Backend.Custom; +using Nub.Lang.Backend; using Nub.Lang.Frontend.Lexing; using Nub.Lang.Frontend.Parsing; using Nub.Lang.Frontend.Typing; @@ -48,9 +48,9 @@ internal static class Program typer.Populate(); var generator = new Generator(definitions); - var asm = generator.Generate(); + var result = generator.Generate(); - File.WriteAllText(output, asm); + File.WriteAllText(output, result); return 0; } diff --git a/lang/Nub.Lang/StructMember.cs b/src/compiler/Nub.Lang/StructField.cs similarity index 55% rename from lang/Nub.Lang/StructMember.cs rename to src/compiler/Nub.Lang/StructField.cs index 600687c..56d9946 100644 --- a/lang/Nub.Lang/StructMember.cs +++ b/src/compiler/Nub.Lang/StructField.cs @@ -2,9 +2,9 @@ namespace Nub.Lang; -public class StructMember(string name, Type type, Optional value) +public class StructField(string name, NubType type, Optional value) { public string Name { get; } = name; - public Type Type { get; } = type; + public NubType Type { get; } = type; public Optional Value { get; } = value; } \ No newline at end of file diff --git a/lang/Nub.Lang/Variant.cs b/src/compiler/Nub.Lang/Variant.cs similarity index 100% rename from lang/Nub.Lang/Variant.cs rename to src/compiler/Nub.Lang/Variant.cs diff --git a/src/runtime/runtime.asm b/src/runtime/runtime.asm new file mode 100644 index 0000000..f710165 --- /dev/null +++ b/src/runtime/runtime.asm @@ -0,0 +1,36 @@ +global _start +extern main + +section .text +_start: + ; Extract argc and argv from the stack + mov rdi, [rsp] ; rdi = argc + lea rsi, [rsp + 8] ; rsi = argv (pointer to array of strings) + + ; Call main(argc, argv) + call main ; main returns int in rax + + ; Exit with main's return value + mov rdi, rax ; exit code + mov rax, 60 ; syscall: exit + syscall + +global nub_strcmp + +nub_strcmp: + xor rdx, rdx +.loop: + mov al, [rsi + rdx] + mov bl, [rdi + rdx] + inc rdx + cmp al, bl + jne .not_equal + cmp al, 0 + je .equal + jmp .loop +.not_equal: + mov rax, 0 + ret +.equal: + mov rax, 1 + ret \ No newline at end of file diff --git a/std/baseline/gc.c b/std/baseline/gc.c deleted file mode 100644 index f2623de..0000000 --- a/std/baseline/gc.c +++ /dev/null @@ -1,264 +0,0 @@ -#include -#include -#include -#include - -#define MINIMUM_THRESHOLD (1024 * 1024 * 8) -#define MINIMUM_BLOCK_SIZE 4096 - -typedef struct alloc_block { - uint64_t mark; - uint64_t size; - struct alloc_block* next; -} alloc_block_t; - -typedef struct free_block { - uint64_t size; - struct free_block* next; -} free_block_t; - -static alloc_block_t* alloc_list_head = NULL; -static free_block_t* free_list_head = NULL; -static void* stack_start = NULL; -static int64_t free_list_size = 0; -static int64_t mark_count = 0; - -/* Bytes allocated since last collect */ -static int64_t bytes_allocated = 0; -/* Threshold for next collect */ -static int64_t trigger_threshold = MINIMUM_THRESHOLD; - -static void* sys_mmap(size_t size); -static void* get_sp(void); -static void gc_collect(void); -static void gc_mark(void* ptr); -static void gc_mark_stack(void); -static void gc_sweep(void); -static int64_t max(int64_t a, int64_t b); -static void insert_into_free(free_block_t* block); -static void merge(free_block_t* block); - -void gc_init(void) { - stack_start = get_sp(); -} - -/* Allocate memory with garbage collection */ -void* gc_alloc(int64_t size) { - size += sizeof(alloc_block_t); // Adjust for metadata size - - if (bytes_allocated > trigger_threshold) { - gc_collect(); - } - - bytes_allocated += size; - - // Search free list for a suitable block - free_block_t* current = free_list_head; - free_block_t* prev = NULL; - - while (current != NULL) { - if (current->size >= size) { - // Found a suitable block - break; - } - prev = current; - current = current->next; - } - - if (current == NULL) { - // No suitable block found, allocate a new one - int64_t alloc_size = max(size, MINIMUM_BLOCK_SIZE); - void* memory = sys_mmap(alloc_size); - - free_block_t* new_block = (free_block_t*)memory; - new_block->size = alloc_size - sizeof(free_block_t); - new_block->next = NULL; - - insert_into_free(new_block); - current = new_block; - - // Recalculate prev - if (current == free_list_head) { - prev = NULL; - } else { - prev = free_list_head; - while (prev->next != current) { - prev = prev->next; - } - } - } - - // Use the block - alloc_block_t* result; - - if (current->size > size) { - // Block is larger than needed, split it - result = (alloc_block_t*)((char*)current + current->size + sizeof(free_block_t) - size); - current->size -= size; - } else { - // Use the entire block - result = (alloc_block_t*)current; - - // Remove block from free list - if (prev == NULL) { - free_list_head = current->next; - } else { - prev->next = current->next; - } - - free_list_size--; - } - - // Initialize metadata - result->mark = 0; - result->size = size - sizeof(alloc_block_t); - result->next = alloc_list_head; - alloc_list_head = result; - - // Return pointer to usable memory - return (void*)(result + 1); -} - -/* Run garbage collection */ -static void gc_collect(void) { - gc_mark_stack(); - gc_sweep(); - trigger_threshold = max(bytes_allocated * 2, MINIMUM_THRESHOLD); - bytes_allocated = 0; -} - -static void gc_mark_stack(void) { - mark_count = 0; - - void** current = get_sp(); - void** end = (void**)stack_start; - - while (current < end) { - gc_mark(*current); - current++; - } -} - -/* Mark a single object and recursively mark its contents */ -static void gc_mark(void* ptr) { - if (ptr == NULL) { - return; - } - - alloc_block_t* block = alloc_list_head; - while (block != NULL) { - void* block_data = (void*)(block + 1); - if (block_data == ptr) { - if (block->mark == 0) { - mark_count++; - block->mark = 1; - - void** p = (void**)block_data; - void** end = (void**)((char*)block_data + block->size); - while (p < end) { - gc_mark(*p); - p++; - } - } - return; - } - block = block->next; - } -} - -static void gc_sweep(void) { - alloc_block_t* current = alloc_list_head; - alloc_block_t* prev = NULL; - - while (current != NULL) { - if (current->mark == 0) { - alloc_block_t* next = current->next; - - if (prev == NULL) { - alloc_list_head = next; - } else { - prev->next = next; - } - - bytes_allocated -= (current->size + sizeof(alloc_block_t)); - - free_block_t* free_block = (free_block_t*)current; - free_block->size = current->size + sizeof(alloc_block_t) - sizeof(free_block_t); - free_block->next = NULL; - - insert_into_free(free_block); - - current = next; - } else { - current->mark = 0; - prev = current; - current = current->next; - } - } -} - -/* Insert a block into the free list, maintaining address order */ -static void insert_into_free(free_block_t* block) { - if (free_list_head == NULL || block < free_list_head) { - // Insert at head - block->next = free_list_head; - free_list_head = block; - free_list_size++; - merge(block); - return; - } - - // Find insertion point - free_block_t* current = free_list_head; - while (current->next != NULL && current->next < block) { - current = current->next; - } - - // Insert after current - block->next = current->next; - current->next = block; - free_list_size++; - - // Try to merge adjacent blocks - merge(current); -} - -static void merge(free_block_t* block) { - while (block->next != NULL) { - char* block_end = (char*)block + block->size + sizeof(free_block_t); - if (block_end == (char*)block->next) { - free_list_size--; - block->size += block->next->size + sizeof(free_block_t); - block->next = block->next->next; - } else { - break; - } - } -} - -static void* sys_mmap(size_t size) { - void* result = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - - if (result == MAP_FAILED) { - perror("[sys_mmap] mmap failed"); - exit(1); - } - - return result; -} - -static int64_t max(int64_t a, int64_t b) { - if (a > b) { - return a; - } else { - return b; - } -} - -void* get_sp(void) { - volatile unsigned long var = 0; -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wreturn-local-addr" - return (void*)((unsigned long)&var + 4); -#pragma GCC diagnostic pop -} \ No newline at end of file diff --git a/std/baseline/str_cmp.asm b/std/baseline/str_cmp.asm deleted file mode 100644 index 4da1302..0000000 --- a/std/baseline/str_cmp.asm +++ /dev/null @@ -1,20 +0,0 @@ -global str_cmp - -section .text -str_cmp: - xor rdx, rdx -.loop: - mov al, [rsi + rdx] - mov bl, [rdi + rdx] - inc rdx - cmp al, bl - jne .not_equal - cmp al, 0 - je .equal - jmp .loop -.not_equal: - mov rax, 0 - ret -.equal: - mov rax, 1 - ret \ No newline at end of file diff --git a/std/core/arr_size.asm b/std/core/arr_size.asm deleted file mode 100644 index 4c6291a..0000000 --- a/std/core/arr_size.asm +++ /dev/null @@ -1,6 +0,0 @@ -global arr_size - -section .text -arr_size: - mov rax, [rdi] - ret \ No newline at end of file diff --git a/std/core/itoa.asm b/std/core/itoa.asm deleted file mode 100644 index 51f0d9e..0000000 --- a/std/core/itoa.asm +++ /dev/null @@ -1,23 +0,0 @@ -section .bss - buffer resb 20 - -section .text - global itoa - -itoa: - mov rax, rdi - mov rsi, buffer + 19 - mov byte [rsi], 0 - dec rsi -.loop: - xor rdx, rdx - mov rcx, 10 - div rcx - add dl, '0' - mov [rsi], dl - dec rsi - test rax, rax - jnz .loop - inc rsi - mov rax, rsi - ret diff --git a/std/core/str_len.asm b/std/core/str_len.asm deleted file mode 100644 index ea7fc00..0000000 --- a/std/core/str_len.asm +++ /dev/null @@ -1,13 +0,0 @@ -global str_len - -section .text -str_len: - xor rax, rax -.loop: - cmp byte [rdi], 0 - jz .done - inc rax - inc rdi - jmp .loop -.done: - ret \ No newline at end of file