From ed5a926b06b73403de18008a4bb514d2674bfd78 Mon Sep 17 00:00:00 2001 From: Aurora Dawn <131844170+StellarWitch7@users.noreply.github.com> Date: Wed, 8 Nov 2023 12:09:32 -0500 Subject: [PATCH] Here be tests --- .../{build-and-test.yml => build.yml} | 11 +- .github/workflows/test.yml | 36 ++ Moth.Unit/Constants.cs | 18 + Moth.Unit/Definitions.cs | 9 + Moth.Unit/Intrinsics.cs | 15 + Moth.Unit/Operators.cs | 388 ++++++++++++++- Moth/AST/Node/ConstantNode.cs | 2 +- Moth/AST/TokenParser.cs | 8 +- Moth/LLVM/LLVMCompiler.cs | 55 ++- Moth/Tokens/Token.cs | 3 +- Moth/Tokens/Tokenizer.cs | 449 +++++++++--------- 11 files changed, 729 insertions(+), 265 deletions(-) rename .github/workflows/{build-and-test.yml => build.yml} (78%) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build.yml similarity index 78% rename from .github/workflows/build-and-test.yml rename to .github/workflows/build.yml index 76a84d9..333c7e7 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build.yml @@ -14,9 +14,9 @@ env: DOTNET_VERSION: '7.0.203' jobs: - build-and-test: + vuild: - name: build-and-test-${{matrix.os}} + name: build-${{matrix.os}} runs-on: ${{ matrix.os }} strategy: matrix: @@ -29,11 +29,8 @@ jobs: with: dotnet-version: ${{ env.DOTNET_VERSION }} - - name: Install dependencies + - name: Install Dependencies run: dotnet restore - + - name: Build run: dotnet build - - - name: Test - run: dotnet test diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..7021ea3 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,36 @@ +name: Unit Tests + +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + paths: + - '**.cs' + - '**.csproj' + +env: + DOTNET_VERSION: '7.0.203' + +jobs: + test: + + name: test-${{matrix.os}} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest] + + steps: + - uses: actions/checkout@v3 + - name: Setup .NET Core + uses: actions/setup-dotnet@v3 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Install Dependencies + run: dotnet restore + + - name: Run Tests + run: dotnet test diff --git a/Moth.Unit/Constants.cs b/Moth.Unit/Constants.cs index 664c621..9ed7829 100644 --- a/Moth.Unit/Constants.cs +++ b/Moth.Unit/Constants.cs @@ -49,4 +49,22 @@ public void String() Assert.AreEqual(expectedGlobal, module.FirstGlobal.ToString()); Assert.AreEqual(expectedMain, module.GetNamedFunction("main").ToString()); } + + [TestMethod] + public void ScientificNotation() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n %pow = call float @llvm.powi.f32.i32(float 1.000000e+01, i32 5)" + + "\n %0 = fptosi float %pow to i32" + + "\n %1 = mul i32 2, %0" + + "\n %val = alloca i32, align 4" + + "\n store i32 %1, ptr %val, align 4" + + "\n ret i32 0" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("local val ?= 2e+5; return 0;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } } diff --git a/Moth.Unit/Definitions.cs b/Moth.Unit/Definitions.cs index 0a41e83..18cc404 100644 --- a/Moth.Unit/Definitions.cs +++ b/Moth.Unit/Definitions.cs @@ -3,6 +3,15 @@ [TestClass] public class Definitions { + [TestMethod] + public void Class() + { + var expected = "%Thing = type { i32, i1, float }"; + var code = Utils.PrependNamespace("public class Thing { public int #i32; public bool #bool; private float #f32; }"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetTypeByName("Thing").ToString()); + } + [TestMethod] public void ObjectAccess() { diff --git a/Moth.Unit/Intrinsics.cs b/Moth.Unit/Intrinsics.cs index 32cfa25..6ff23da 100644 --- a/Moth.Unit/Intrinsics.cs +++ b/Moth.Unit/Intrinsics.cs @@ -17,4 +17,19 @@ public void SizeOf() var module = Utils.FullCompile(code); Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); } + + [TestMethod] + public void AlignOf() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n %val = alloca i64, align 8" + + "\n store i64 ptrtoint (ptr getelementptr ({ i1, i32 }, ptr null, i64 0, i32 1) to i64), ptr %val, align 4" + + "\n ret i32 0" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("local val ?= #i32.alignof(); return 0;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } } diff --git a/Moth.Unit/Operators.cs b/Moth.Unit/Operators.cs index c40f9a3..afa8c8c 100644 --- a/Moth.Unit/Operators.cs +++ b/Moth.Unit/Operators.cs @@ -4,7 +4,7 @@ namespace Moth.Unit; public class Operators { [TestMethod] - public void Addition() + public void AddInt() { var expected = "define i32 @main() {" + "\nentry:" + @@ -17,7 +17,7 @@ public void Addition() } [TestMethod] - public void Subtraction() + public void SubInt() { var expected = "define i32 @main() {" + "\nentry:" + @@ -30,7 +30,7 @@ public void Subtraction() } [TestMethod] - public void Multiplication() + public void MulInt() { var expected = "define i32 @main() {" + "\nentry:" + @@ -43,7 +43,7 @@ public void Multiplication() } [TestMethod] - public void Division() + public void DivInt() { var expected = "define i32 @main() {" + "\nentry:" + @@ -56,7 +56,7 @@ public void Division() } [TestMethod] - public void Modulo() + public void ModInt() { var expected = "define i32 @main() {" + "\nentry:" + @@ -69,7 +69,7 @@ public void Modulo() } [TestMethod] - public void Exponential() + public void ExpInt() { var expected = "define i32 @main() {" + "\nentry:" + @@ -84,7 +84,7 @@ public void Exponential() } [TestMethod] - public void AddAssign() + public void AddAssignInt() { var expected = "define i32 @main() {" + "\nentry:" + @@ -105,7 +105,7 @@ public void AddAssign() } [TestMethod] - public void SubAssign() + public void SubAssignInt() { var expected = "define i32 @main() {" + "\nentry:" + @@ -126,7 +126,7 @@ public void SubAssign() } [TestMethod] - public void MulAssign() + public void MulAssignInt() { var expected = "define i32 @main() {" + "\nentry:" + @@ -147,7 +147,7 @@ public void MulAssign() } [TestMethod] - public void DivAssign() + public void DivAssignInt() { var expected = "define i32 @main() {" + "\nentry:" + @@ -168,7 +168,7 @@ public void DivAssign() } [TestMethod] - public void ModAssign() + public void ModAssignInt() { var expected = "define i32 @main() {" + "\nentry:" + @@ -189,7 +189,7 @@ public void ModAssign() } [TestMethod] - public void ExpAssign() + public void ExpAssignInt() { var expected = "define i32 @main() {" + "\nentry:" + @@ -336,7 +336,7 @@ public void Casti32u32() { var expected = "define i32 @main() {" + "\nentry:" + - "\n %val = alloca i32, align 8" + + "\n %val = alloca i32, align 4" + "\n store i32 -6, ptr %val, align 4" + "\n ret i32 0" + "\n}" + @@ -346,6 +346,36 @@ public void Casti32u32() Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); } + [TestMethod] + public void Castu1i32() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n %val = alloca i32, align 4" + + "\n store i32 1, ptr %val, align 4" + + "\n ret i32 0" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("local val ?= #i32 <- true; return 0;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } + + [TestMethod] + public void Casti32u1() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n %val = alloca i1, align 1" + + "\n store i1 true, ptr %val, align 1" + + "\n ret i32 0" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("local val ?= #bool <- 2; return 0;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } + [TestMethod] public void Casti32i64() { @@ -360,4 +390,336 @@ public void Casti32i64() var module = Utils.FullCompile(code); Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); } + + [TestMethod] + public void Casti32f32() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n %val = alloca float, align 4" + + "\n store float 7.000000e+00, ptr %val, align 4" + + "\n ret i32 0" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("local val ?= #f32 <- 7; return 0;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } + + [TestMethod] + public void Casti32f64() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n %val = alloca double, align 8" + + "\n store double 7.000000e+00, ptr %val, align 8" + + "\n ret i32 0" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("local val ?= #f64 <- 7; return 0;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } + + [TestMethod] + public void Castf32f64() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n %val = alloca double, align 8" + + "\n store double 7.000000e+00, ptr %val, align 8" + + "\n ret i32 0" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("local val ?= #f64 <- 7.0; return 0;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } + + [TestMethod] + public void Castf32i32() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n %val = alloca i32, align 4" + + "\n store i32 7, ptr %val, align 4" + + "\n ret i32 0" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("local val ?= #i32 <- 7.0; return 0;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } + + [TestMethod] + public void Castf32u32() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n %val = alloca i32, align 4" + + "\n store i32 7, ptr %val, align 4" + + "\n ret i32 0" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("local val ?= #u32 <- 7.0; return 0;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } + + [TestMethod] + public void Castf32i64() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n %val = alloca i64, align 8" + + "\n store i64 7, ptr %val, align 4" + + "\n ret i32 0" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("local val ?= #i64 <- 7.0; return 0;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } + + [TestMethod] + public void AddFloat() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n ret i32 6" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("return #i32 <- 4.0 + 2.0;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } + + [TestMethod] + public void SubFloat() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n ret i32 2" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("return #i32 <- 4.0 - 2.0;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } + + [TestMethod] + public void MulFloat() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n ret i32 8" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("return #i32 <- 4.0 * 2.0;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } + + [TestMethod] + public void DivFloat() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n ret i32 2" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("return #i32 <- 4.0 / 2.0;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } + + [TestMethod] + public void ModFloat() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n ret i32 0" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("return #i32 <- 4.0 % 2.0;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } + + [TestMethod] + public void ExpFloat() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n %pow = call float @llvm.pow.f32(float 4.000000e+00, float 2.000000e+00)" + + "\n %0 = fptosi float %pow to i32" + + "\n ret i32 %0" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("return #i32 <- 4.0 ^ 2.0;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } + + [TestMethod] + public void AddAssignFloat() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n %val = alloca float, align 4" + + "\n store float 2.000000e+00, ptr %val, align 4" + + "\n %0 = load float, ptr %val, align 4" + + "\n %1 = fadd float %0, 2.000000e+00" + + "\n store float %1, ptr %val, align 4" + + "\n %2 = load float, ptr %val, align 4" + + "\n %3 = fptosi float %2 to i32" + + "\n ret i32 %3" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("local val #f32 = 2.0;" + + "\n val += 2.0;" + + "\n return #i32 <- val;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } + + [TestMethod] + public void SubAssignFloat() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n %val = alloca float, align 4" + + "\n store float 2.000000e+00, ptr %val, align 4" + + "\n %0 = load float, ptr %val, align 4" + + "\n %1 = fsub float %0, 2.000000e+00" + + "\n store float %1, ptr %val, align 4" + + "\n %2 = load float, ptr %val, align 4" + + "\n %3 = fptosi float %2 to i32" + + "\n ret i32 %3" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("local val #f32 = 2.0;" + + "\n val -= 2.0;" + + "\n return #i32 <- val;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } + + [TestMethod] + public void MulAssignFloat() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n %val = alloca float, align 4" + + "\n store float 2.000000e+00, ptr %val, align 4" + + "\n %0 = load float, ptr %val, align 4" + + "\n %1 = fmul float %0, 2.000000e+00" + + "\n store float %1, ptr %val, align 4" + + "\n %2 = load float, ptr %val, align 4" + + "\n %3 = fptosi float %2 to i32" + + "\n ret i32 %3" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("local val #f32 = 2.0;" + + "\n val *= 2.0;" + + "\n return #i32 <- val;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } + + [TestMethod] + public void DivAssignFloat() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n %val = alloca float, align 4" + + "\n store float 2.000000e+00, ptr %val, align 4" + + "\n %0 = load float, ptr %val, align 4" + + "\n %1 = fdiv float %0, 2.000000e+00" + + "\n store float %1, ptr %val, align 4" + + "\n %2 = load float, ptr %val, align 4" + + "\n %3 = fptosi float %2 to i32" + + "\n ret i32 %3" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("local val #f32 = 2.0;" + + "\n val /= 2.0;" + + "\n return #i32 <- val;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } + + [TestMethod] + public void ModAssignFloat() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n %val = alloca float, align 4" + + "\n store float 2.000000e+00, ptr %val, align 4" + + "\n %0 = load float, ptr %val, align 4" + + "\n %1 = frem float %0, 2.000000e+00" + + "\n store float %1, ptr %val, align 4" + + "\n %2 = load float, ptr %val, align 4" + + "\n %3 = fptosi float %2 to i32" + + "\n ret i32 %3" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("local val #f32 = 2.0;" + + "\n val %= 2.0;" + + "\n return #i32 <- val;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } + + [TestMethod] + public void ExpAssignFloat() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n %val = alloca float, align 4" + + "\n store float 2.000000e+00, ptr %val, align 4" + + "\n %0 = load float, ptr %val, align 4" + + "\n %pow = call float @llvm.pow.f32(float %0, float 2.000000e+00)" + + "\n store float %pow, ptr %val, align 4" + + "\n %1 = load float, ptr %val, align 4" + + "\n %2 = fptosi float %1 to i32" + + "\n ret i32 %2" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("local val #f32 = 2.0;" + + "\n val ^= 2.0;" + + "\n return #i32 <- val;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } + + [TestMethod] + public void Equal() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n %val = alloca i1, align 1" + + "\n store i1 false, ptr %val, align 1" + + "\n ret i32 0" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("local val #bool = 4 == 2; return 0;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } + + [TestMethod] + public void NotEqual() + { + var expected = "define i32 @main() {" + + "\nentry:" + + "\n %val = alloca i1, align 1" + + "\n store i1 true, ptr %val, align 1" + + "\n ret i32 0" + + "\n}" + + "\n"; + var code = Utils.BasicWrap("local val #bool = 4 != 2; return 0;"); + var module = Utils.FullCompile(code); + Assert.AreEqual(expected, module.GetNamedFunction("main").ToString()); + } } \ No newline at end of file diff --git a/Moth/AST/Node/ConstantNode.cs b/Moth/AST/Node/ConstantNode.cs index 77e6123..bbb0c40 100644 --- a/Moth/AST/Node/ConstantNode.cs +++ b/Moth/AST/Node/ConstantNode.cs @@ -4,7 +4,7 @@ namespace Moth.AST.Node; public class ConstantNode : ExpressionNode { - public object Value { get; set; } + public object? Value { get; set; } public ConstantNode(object? value) { diff --git a/Moth/AST/TokenParser.cs b/Moth/AST/TokenParser.cs index 8207021..9358724 100644 --- a/Moth/AST/TokenParser.cs +++ b/Moth/AST/TokenParser.cs @@ -514,7 +514,7 @@ public static IfNode ProcessIf(ParseContext context) if (context.Current?.Type == TokenType.If) { - context.MoveNext(); + context.MoveNext(); //TODO: this does not work in compilation return new ScopeNode(new List { ProcessIf(context) @@ -836,6 +836,12 @@ public static ExpressionNode ProcessExpression(ParseContext context, ExpressionN break; } + case TokenType.ScientificNotation: + context.MoveNext(); + lastCreatedNode = new BinaryOperationNode(lastCreatedNode, + ProcessBinaryOp(context, OperationType.Exponential, new ConstantNode(10)), + OperationType.Multiplication); + break; case TokenType.Not: context.MoveNext(); lastCreatedNode = new InverseNode(ProcessAccess(context)); diff --git a/Moth/LLVM/LLVMCompiler.cs b/Moth/LLVM/LLVMCompiler.cs index 3c35add..e24c41b 100644 --- a/Moth/LLVM/LLVMCompiler.cs +++ b/Moth/LLVM/LLVMCompiler.cs @@ -640,8 +640,8 @@ public static ValueContext CompileLiteral(CompilerContext compiler, Scope scope, public static ValueContext CompileOperation(CompilerContext compiler, Scope scope, BinaryOperationNode binaryOp) { - var left = CompileExpression(compiler, scope, binaryOp.Left); - var right = CompileExpression(compiler, scope, binaryOp.Right); + var left = SafeLoad(compiler, CompileExpression(compiler, scope, binaryOp.Left)); + var right = SafeLoad(compiler, CompileExpression(compiler, scope, binaryOp.Right)); if (binaryOp.Type == OperationType.Exponential && right.Type.Class is Float or Int @@ -656,9 +656,11 @@ public static ValueContext CompileOperation(CompilerContext compiler, Scope scop LLVMValueRef leftVal; LLVMValueRef rightVal; LLVMValueRef builtVal; + Type builtType; - leftVal = SafeLoad(compiler, left).LLVMValue; - rightVal = SafeLoad(compiler, right).LLVMValue; + leftVal = left.LLVMValue; + rightVal = right.LLVMValue; + builtType = left.Type; switch (binaryOp.Type) { @@ -772,6 +774,8 @@ public static ValueContext CompileOperation(CompilerContext compiler, Scope scop throw new NotImplementedException(); } + builtType = UnsignedInt.Bool.Type; + break; case OperationType.GreaterThan: case OperationType.GreaterThanOrEqual: @@ -820,7 +824,7 @@ public static ValueContext CompileOperation(CompilerContext compiler, Scope scop throw new NotImplementedException(); } - return new ValueContext(left.Type is RefType @ref ? @ref.BaseType : left.Type, builtVal); + return new ValueContext(builtType, builtVal); } else { @@ -836,7 +840,7 @@ public static ValueContext CompileCast(CompilerContext compiler, Scope scope, Bi throw new Exception($"Cast destination (\"{binaryOp.Left}\") is invalid."); } - var right = CompileExpression(compiler, scope, binaryOp.Right); + var right = SafeLoad(compiler, CompileExpression(compiler, scope, binaryOp.Right)); Type destType = ResolveTypeRef(compiler, left); LLVMValueRef builtVal; @@ -844,24 +848,29 @@ public static ValueContext CompileCast(CompilerContext compiler, Scope scope, Bi { if (right.Type.Class is Int) { - if (destType.Class.GetType() != right.Type.Class.GetType()) + if (destType.Class.Name == Reserved.Bool) + { + builtVal = compiler.Builder.BuildICmp(LLVMIntPredicate.LLVMIntNE, + LLVMValueRef.CreateConstInt(right.Type.LLVMType, 0), right.LLVMValue); + } + else if (right.Type.Class.Name == Reserved.Bool) { - throw new NotImplementedException("Casting between signed and unsigned int not supported yet."); + builtVal = compiler.Builder.BuildZExt(right.LLVMValue, destType.LLVMType); } else { - builtVal = compiler.Builder.BuildIntCast(SafeLoad(compiler, right).LLVMValue, destType.LLVMType); + builtVal = compiler.Builder.BuildIntCast(right.LLVMValue, destType.LLVMType); } } else if (right.Type.Class is Float) { if (destType.Class is UnsignedInt) { - builtVal = compiler.Builder.BuildFPToUI(SafeLoad(compiler, right).LLVMValue, destType.LLVMType); + builtVal = compiler.Builder.BuildFPToUI(right.LLVMValue, destType.LLVMType); } else if (destType.Class is SignedInt) { - builtVal = compiler.Builder.BuildFPToSI(SafeLoad(compiler, right).LLVMValue, destType.LLVMType); + builtVal = compiler.Builder.BuildFPToSI(right.LLVMValue, destType.LLVMType); } else { @@ -877,17 +886,17 @@ public static ValueContext CompileCast(CompilerContext compiler, Scope scope, Bi { if (right.Type.Class is Float) { - builtVal = compiler.Builder.BuildFPCast(SafeLoad(compiler, right).LLVMValue, destType.LLVMType); + builtVal = compiler.Builder.BuildFPCast(right.LLVMValue, destType.LLVMType); } else if (right.Type.Class is Int) { if (right.Type.Class is UnsignedInt) { - builtVal = compiler.Builder.BuildUIToFP(SafeLoad(compiler, right).LLVMValue, destType.LLVMType); + builtVal = compiler.Builder.BuildUIToFP(right.LLVMValue, destType.LLVMType); } else if (right.Type.Class is SignedInt) { - builtVal = compiler.Builder.BuildSIToFP(SafeLoad(compiler, right).LLVMValue, destType.LLVMType); + builtVal = compiler.Builder.BuildSIToFP(right.LLVMValue, destType.LLVMType); } else { @@ -902,7 +911,7 @@ public static ValueContext CompileCast(CompilerContext compiler, Scope scope, Bi else { builtVal = compiler.Builder.BuildCast(LLVMOpcode.LLVMBitCast, - SafeLoad(compiler, right).LLVMValue, + right.LLVMValue, destType.LLVMType); } @@ -1115,9 +1124,9 @@ public static ValueContext CompileRef(CompilerContext compiler, Scope scope, Ref var @class = compiler.GetClass(UnVoid(typeRef)); refNode = refNode.Child; - if (refNode is FuncCallNode methodCall) + if (refNode is FuncCallNode funcCall) { - context = CompileFuncCall(compiler, context, scope, methodCall, @class); + context = CompileFuncCall(compiler, context, scope, funcCall, @class); refNode = refNode.Child; } else @@ -1126,9 +1135,9 @@ public static ValueContext CompileRef(CompilerContext compiler, Scope scope, Ref throw new NotImplementedException(); } } - else if (refNode is FuncCallNode methodCall) + else if (refNode is FuncCallNode funcCall) { - context = CompileFuncCall(compiler, context, scope, methodCall); + context = CompileFuncCall(compiler, context, scope, funcCall); refNode = refNode.Child; } else if (refNode is IndexAccessNode indexAccess) @@ -1193,7 +1202,7 @@ public static ValueContext CompileVarRef(CompilerContext compiler, ValueContext } } - public static ValueContext CompileFuncCall(CompilerContext compiler, ValueContext context, Scope scope, FuncCallNode methodCall, + public static ValueContext CompileFuncCall(CompilerContext compiler, ValueContext context, Scope scope, FuncCallNode funcCall, Class staticClass = null) { List argTypes = new List(); @@ -1210,14 +1219,14 @@ public static ValueContext CompileFuncCall(CompilerContext compiler, ValueContex } } - foreach (ExpressionNode arg in methodCall.Arguments) + foreach (ExpressionNode arg in funcCall.Arguments) { var val = CompileExpression(compiler, scope, arg); argTypes.Add(val.Type is RefType @ref ? @ref.BaseType : val.Type); args.Add(SafeLoad(compiler, val).LLVMValue); } - Signature sig = new Signature(methodCall.Name, argTypes); + Signature sig = new Signature(funcCall.Name, argTypes); if (context != null && context.Type.Class.Methods.TryGetValue(sig, out func)) { @@ -1242,7 +1251,7 @@ public static ValueContext CompileFuncCall(CompilerContext compiler, ValueContex } else { - throw new Exception($"Function \"{methodCall.Name}\" does not exist."); + throw new Exception($"Function \"{funcCall.Name}\" does not exist."); } return new ValueContext(func.ReturnType, diff --git a/Moth/Tokens/Token.cs b/Moth/Tokens/Token.cs index dc62360..e560287 100644 --- a/Moth/Tokens/Token.cs +++ b/Moth/Tokens/Token.cs @@ -104,5 +104,6 @@ public enum TokenType DivAssign, ModAssign, ExpAssign, - DeRef + DeRef, + ScientificNotation } \ No newline at end of file diff --git a/Moth/Tokens/Tokenizer.cs b/Moth/Tokens/Tokenizer.cs index 5328018..91844e6 100644 --- a/Moth/Tokens/Tokenizer.cs +++ b/Moth/Tokens/Tokenizer.cs @@ -11,130 +11,141 @@ public static List Tokenize(string text) while (stream.Current is {} ch) { - switch (ch) - { - case '\n' or '\r' or '\t' or ' ': - break; - - //Skip comments - case '/' when stream.Next is '/': - { - while (stream.MoveNext(out ch)) - { - if (ch != '\n') continue; - break; - } - - break; - } - - //Parse character constants - case '\'': - { - stream.Position++; - - if (stream.Current == '\'') - { - throw new TokenizerException() - { + switch (ch) + { + case '\n' or '\r' or '\t' or ' ': + break; + + //Skip comments + case '/' when stream.Next is '/': + { + while (stream.MoveNext(out ch)) + { + if (ch != '\n') continue; + break; + } + + break; + } + + case 'e' when stream.Next is '+': + { + stream.Position++; + tokens.Add(new Token() + { + Type = TokenType.ScientificNotation, + Text = "e+".AsMemory(), + }); + break; + } + + //Parse character constants + case '\'': + { + stream.Position++; + + if (stream.Current == '\'') + { + throw new TokenizerException() + { Character = (char)stream.Current, Line = stream.CurrentLine, Column = stream.CurrentColumn, Position = stream.Position, }; - } - else - { - tokens.Add(new Token() - { - Type = TokenType.LiteralChar, - Text = $"{ProcessCharacter(ref stream)}".AsMemory(), - }); - - stream.Position++; - - if (stream.Current == '\'') - { + } + else + { + tokens.Add(new Token() + { + Type = TokenType.LiteralChar, + Text = $"{ProcessCharacter(ref stream)}".AsMemory(), + }); + + stream.Position++; + + if (stream.Current == '\'') + { break; } - else - { - throw new TokenizerException() - { + else + { + throw new TokenizerException() + { Character = (char)stream.Current, Line = stream.CurrentLine, Column = stream.CurrentColumn, Position = stream.Position, }; - } + } } - } - - //Parse keywords or names - case >= 'a' and <= 'z': - case >= 'A' and <= 'Z': - case '_': - { - var keyword = stream.Peek(c => char.IsLetterOrDigit(c) || c == '_'); - tokens.Add(new Token - { - Text = keyword, - Type = keyword.Span switch - { - "if" => TokenType.If, - "ref" => TokenType.Ref, - "load" => TokenType.DeRef, - "null" => TokenType.Null, - "local" => TokenType.Local, - "self" => TokenType.This, - "namespace" => TokenType.Namespace, - "then" => TokenType.Then, - "constant" => TokenType.Constant, - "while" => TokenType.While, - "true" => TokenType.True, - "pi" => TokenType.Pi, - "else" => TokenType.Else, - "false" => TokenType.False, - "every" => TokenType.For, - "in" => TokenType.In, - "or" => TokenType.Or, - "and" => TokenType.And, - "func" => TokenType.Function, - "class" => TokenType.Class, - "use" => TokenType.Import, - "public" => TokenType.Public, - "static" => TokenType.Static, - "return" => TokenType.Return, - "private" => TokenType.Private, - "foreign" => TokenType.Foreign, - _ => TokenType.Name, - }, - }); - - stream.Position += keyword.Length - 1; - break; - } - - //Parse strings + } + + //Parse keywords or names + case >= 'a' and <= 'z': + case >= 'A' and <= 'Z': + case '_': + { + var keyword = stream.Peek(c => char.IsLetterOrDigit(c) || c == '_'); + tokens.Add(new Token + { + Text = keyword, + Type = keyword.Span switch + { + "if" => TokenType.If, + "ref" => TokenType.Ref, + "load" => TokenType.DeRef, + "null" => TokenType.Null, + "local" => TokenType.Local, + "self" => TokenType.This, + "namespace" => TokenType.Namespace, + "then" => TokenType.Then, + "constant" => TokenType.Constant, + "while" => TokenType.While, + "true" => TokenType.True, + "pi" => TokenType.Pi, + "else" => TokenType.Else, + "false" => TokenType.False, + "every" => TokenType.For, + "in" => TokenType.In, + "or" => TokenType.Or, + "and" => TokenType.And, + "func" => TokenType.Function, + "class" => TokenType.Class, + "use" => TokenType.Import, + "public" => TokenType.Public, + "static" => TokenType.Static, + "return" => TokenType.Return, + "private" => TokenType.Private, + "foreign" => TokenType.Foreign, + _ => TokenType.Name, + }, + }); + + stream.Position += keyword.Length - 1; + break; + } + + //Parse strings case '"': { stream.Position++; - var builder = new StringBuilder(); - - while (stream.Current != null) - { - if (stream.Current == '"') - { - break; - } - else - { + var builder = new StringBuilder(); + + while (stream.Current != null) + { + if (stream.Current == '"') + { + break; + } + else + { builder.Append(ProcessCharacter(ref stream)); - stream.Position++; - } - } + stream.Position++; + } + } - string @string = builder.ToString(); + string @string = builder.ToString(); tokens.Add(new Token { Text = @string.AsMemory(), @@ -144,10 +155,10 @@ public static List Tokenize(string text) break; } - case '#' when char.IsLetter((char)stream.Next): - case '?' when char.IsLetter((char)stream.Next): - { - char character = (char)stream.Current; + case '#' when char.IsLetter((char)stream.Next): + case '?' when char.IsLetter((char)stream.Next): + { + char character = (char)stream.Current; tokens.Add(new Token() { Text = $"{character}".AsMemory(), @@ -159,122 +170,122 @@ public static List Tokenize(string text) // Parse symbols case var _ when char.IsSymbol(ch) || char.IsPunctuation(ch): - { - var next = stream.Next; - TokenType? type = ch switch - { + { + var next = stream.Next; + TokenType? type = ch switch + { '.' when next is '.' => TokenType.Range, - '=' when next is '=' => TokenType.Equal, - '!' when next is '=' => TokenType.NotEqual, - '<' when next is '=' => TokenType.LesserThanOrEqual, - '>' when next is '=' => TokenType.GreaterThanOrEqual, - '+' when next is '=' => TokenType.AddAssign, - '-' when next is '=' => TokenType.SubAssign, - '*' when next is '=' => TokenType.MulAssign, - '/' when next is '=' => TokenType.DivAssign, - '%' when next is '=' => TokenType.ModAssign, - '^' when next is '=' => TokenType.ExpAssign, - '+' when next is '+' => TokenType.Increment, - '-' when next is '-' => TokenType.Decrement, - '~' when next is '~' => TokenType.Variadic, - '?' when next is '=' => TokenType.InferAssign, - '<' when next is '-' => TokenType.Cast, - '<' when next is '\\' => TokenType.OpeningGenericBracket, - '\\' when next is '>' => TokenType.ClosingGenericBracket, - ':' => TokenType.Colon, - '^' => TokenType.Exponential, - ',' => TokenType.Comma, - '.' => TokenType.Period, - ';' => TokenType.Semicolon, - '{' => TokenType.OpeningCurlyBraces, - '}' => TokenType.ClosingCurlyBraces, - '(' => TokenType.OpeningParentheses, - ')' => TokenType.ClosingParentheses, - '[' => TokenType.OpeningSquareBrackets, - ']' => TokenType.ClosingSquareBrackets, - '>' => TokenType.GreaterThan, - '<' => TokenType.LesserThan, - '|' => TokenType.Or, - '&' => TokenType.And, - '!' => TokenType.Not, - '+' => TokenType.Plus, - '/' => TokenType.ForwardSlash, - '-' => TokenType.Hyphen, - '*' => TokenType.Asterix, - '%' => TokenType.Modulo, - '=' => TokenType.Assign, - '@' => TokenType.AttributeMarker, - - _ => throw new TokenizerException - { - Character = ch, - Line = stream.CurrentLine, - Column = stream.CurrentColumn, - Position = stream.Position, - }, - }; - - var newToken = new Token - { - Text = type switch - { + '=' when next is '=' => TokenType.Equal, + '!' when next is '=' => TokenType.NotEqual, + '<' when next is '=' => TokenType.LesserThanOrEqual, + '>' when next is '=' => TokenType.GreaterThanOrEqual, + '+' when next is '=' => TokenType.AddAssign, + '-' when next is '=' => TokenType.SubAssign, + '*' when next is '=' => TokenType.MulAssign, + '/' when next is '=' => TokenType.DivAssign, + '%' when next is '=' => TokenType.ModAssign, + '^' when next is '=' => TokenType.ExpAssign, + '+' when next is '+' => TokenType.Increment, + '-' when next is '-' => TokenType.Decrement, + '~' when next is '~' => TokenType.Variadic, + '?' when next is '=' => TokenType.InferAssign, + '<' when next is '-' => TokenType.Cast, + '<' when next is '\\' => TokenType.OpeningGenericBracket, + '\\' when next is '>' => TokenType.ClosingGenericBracket, + ':' => TokenType.Colon, + '^' => TokenType.Exponential, + ',' => TokenType.Comma, + '.' => TokenType.Period, + ';' => TokenType.Semicolon, + '{' => TokenType.OpeningCurlyBraces, + '}' => TokenType.ClosingCurlyBraces, + '(' => TokenType.OpeningParentheses, + ')' => TokenType.ClosingParentheses, + '[' => TokenType.OpeningSquareBrackets, + ']' => TokenType.ClosingSquareBrackets, + '>' => TokenType.GreaterThan, + '<' => TokenType.LesserThan, + '|' => TokenType.Or, + '&' => TokenType.And, + '!' => TokenType.Not, + '+' => TokenType.Plus, + '/' => TokenType.ForwardSlash, + '-' => TokenType.Hyphen, + '*' => TokenType.Asterix, + '%' => TokenType.Modulo, + '=' => TokenType.Assign, + '@' => TokenType.AttributeMarker, + + _ => throw new TokenizerException + { + Character = ch, + Line = stream.CurrentLine, + Column = stream.CurrentColumn, + Position = stream.Position, + }, + }; + + var newToken = new Token + { + Text = type switch + { TokenType.Cast or TokenType.Variadic or TokenType.InferAssign or TokenType.AddAssign or TokenType.SubAssign - or TokenType.MulAssign or TokenType.DivAssign - or TokenType.ModAssign or TokenType.ExpAssign + or TokenType.MulAssign or TokenType.DivAssign + or TokenType.ModAssign or TokenType.ExpAssign or TokenType.Increment or TokenType.Decrement or TokenType.OpeningGenericBracket or TokenType.ClosingGenericBracket or TokenType.LesserThanOrEqual or TokenType.GreaterThanOrEqual or TokenType.Equal or TokenType.NotEqual => stream.Peek(2), - _ => stream.Peek(1), - }, - Type = (TokenType)type, - }; - - tokens.Add(newToken); - stream.Position += newToken.Text.Length - 1; - break; - } - - case >= '0' and <= '9': - { - var number = stream.Peek(c => char.IsDigit(c) || c == '.'); - var dots = 0; - var numberSpan = number.Span; - for (var i = 0; i < numberSpan.Length; i++) - { - if (numberSpan[i] == '.') dots++; - if (dots >= 2) - throw new TokenizerException - { - Character = numberSpan[i], - Position = stream.Position + i + 1, - Column = stream.CurrentColumn + i + 1, - Line = stream.CurrentLine, - }; - } - - tokens.Add(new Token - { - Text = number, - Type = number.Span.Contains('.') ? TokenType.LiteralFloat : TokenType.LiteralInt, - }); - - stream.Position += number.Length - 1; - break; - } - - default: - throw new TokenizerException - { - Character = ch, - Line = stream.CurrentLine, - Column = stream.CurrentColumn, - Position = stream.Position, - }; - } - - stream.MoveNext(); + _ => stream.Peek(1), + }, + Type = (TokenType)type, + }; + + tokens.Add(newToken); + stream.Position += newToken.Text.Length - 1; + break; + } + + case >= '0' and <= '9': + { + var number = stream.Peek(c => char.IsDigit(c) || c == '.'); + var dots = 0; + var numberSpan = number.Span; + for (var i = 0; i < numberSpan.Length; i++) + { + if (numberSpan[i] == '.') dots++; + if (dots >= 2) + throw new TokenizerException + { + Character = numberSpan[i], + Position = stream.Position + i + 1, + Column = stream.CurrentColumn + i + 1, + Line = stream.CurrentLine, + }; + } + + tokens.Add(new Token + { + Text = number, + Type = number.Span.Contains('.') ? TokenType.LiteralFloat : TokenType.LiteralInt, + }); + + stream.Position += number.Length - 1; + break; + } + + default: + throw new TokenizerException + { + Character = ch, + Line = stream.CurrentLine, + Column = stream.CurrentColumn, + Position = stream.Position, + }; + } + + stream.MoveNext(); } return tokens;