From d793dc69fe7822a14fe2414befed5e89258f6451 Mon Sep 17 00:00:00 2001 From: Stephen Reindl <4533301+steven-r@users.noreply.github.com> Date: Tue, 19 Nov 2024 12:10:42 +0100 Subject: [PATCH] Feature/issue 6 handle type string (#112) * build: remove global.json * build: remove .netstandard dependency and old msbuild dependencies * tests: add more tests for coverage * build: remove unused dependency on Microsoft.CodeAnalysis.CSharp.Workspaces * refactor: remove SQ complaint * test: add more tests for better coverage * feat: add parameterized `ToString` functions for REAL and BOOLEAN Fixes #111 --------- Co-authored-by: Stephen Reindl --- .../Types/StringTests.cs | 70 ++++++++++ Oberon0.Generator.MsilBin/CreateBinary.cs | 4 +- .../Oberon0.Generator.MsilBin.csproj | 1 - .../Oberon0.System.Tests.csproj | 33 +++++ .../Oberon0System.MathTests.cs | 20 +++ .../Oberon0System.StringsTests.cs | 77 +++++++++++ Oberon0.System/Oberon0System.Strings.cs | 126 +++++++++++------- UnitTestProject1/Types/StringTests.cs | 20 +++ oberon0.sln | 22 +++ .../Internal/ArithmeticOperationAttribute.cs | 20 ++- oberon0/Expressions/Operations/OpRelop.cs | 20 ++- 11 files changed, 344 insertions(+), 69 deletions(-) create mode 100644 Oberon0.System.Tests/Oberon0.System.Tests.csproj create mode 100644 Oberon0.System.Tests/Oberon0System.MathTests.cs create mode 100644 Oberon0.System.Tests/Oberon0System.StringsTests.cs diff --git a/Oberon0.Generator.MsilBin.Tests/Types/StringTests.cs b/Oberon0.Generator.MsilBin.Tests/Types/StringTests.cs index deb9c71..2c464c2 100644 --- a/Oberon0.Generator.MsilBin.Tests/Types/StringTests.cs +++ b/Oberon0.Generator.MsilBin.Tests/Types/StringTests.cs @@ -5,6 +5,7 @@ // -------------------------------------------------------------------------------------------------------------------- #endregion +using System.Globalization; using System.IO; using Microsoft.CodeAnalysis.CSharp; using Xunit; @@ -182,4 +183,73 @@ END Test. Assert.Equal("", output1.ToString()); } + [Theory] + [InlineData(0.0, "0", "G")] + [InlineData(1.0, "1", "G")] + [InlineData(10000000000000.00001, "10000000000000", "G")] + [InlineData(0.1, "0.1", "G")] + [InlineData(2.3E-06, "2.3E-06", "G")] + [InlineData(2.3E06, "2300000", "G")] + [InlineData(double.MinValue, "-1.7976931348623157E+308", "G")] + public void TestToStringRealFormat(double value, string expected, string format) + { + string source = $""" + MODULE Test; + VAR + r: REAL; + s: STRING; + b: INTEGER; + + BEGIN + r := {value.ToString(CultureInfo.InvariantCulture)}; + s := ToString(r, '{format}'); + WriteString(s) + END Test. + """; + var cg = CompileHelper.CompileOberon0Code(source, out string code, output); + Assert.NotEmpty(code); + + var syntaxTree = CSharpSyntaxTree.ParseText(code); + + byte[] assembly = syntaxTree.CompileAndLoadAssembly(cg, true); + Assert.NotNull(assembly); + + using var output1 = new StringWriter(); + Runner.Execute(assembly, output1); + Assert.Equal(expected, output1.ToString()); + } + + [Theory] + [InlineData(false, "false", "true", "false")] + [InlineData(true, "true", "true", "false")] + [InlineData(false, "0", "1", "0")] + [InlineData(true, "1", "1", "0")] + public void TestToStringBooleanFormat(bool value, string expected, string trueVal, string falseVal) + { + string source = $""" + MODULE Test; + VAR + r: BOOLEAN; + s: STRING; + b: INTEGER; + + BEGIN + r := {value.ToString().ToUpper()}; + s := ToString(r, '{trueVal}', '{falseVal}'); + WriteString(s) + END Test. + """; + var cg = CompileHelper.CompileOberon0Code(source, out string code, output); + Assert.NotEmpty(code); + + var syntaxTree = CSharpSyntaxTree.ParseText(code); + + byte[] assembly = syntaxTree.CompileAndLoadAssembly(cg, true); + Assert.NotNull(assembly); + + using var output1 = new StringWriter(); + Runner.Execute(assembly, output1); + Assert.Equal(expected, output1.ToString()); + } + } \ No newline at end of file diff --git a/Oberon0.Generator.MsilBin/CreateBinary.cs b/Oberon0.Generator.MsilBin/CreateBinary.cs index 1df48a8..407a39a 100644 --- a/Oberon0.Generator.MsilBin/CreateBinary.cs +++ b/Oberon0.Generator.MsilBin/CreateBinary.cs @@ -12,6 +12,7 @@ using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; +using Oberon0.Compiler.Exceptions; using Oberon0.Shared; namespace Oberon0.Generator.MsilBin @@ -49,8 +50,7 @@ private static string GetDotnetExe () private CreateBinaryOptions SetOptions(CreateBinaryOptions options) { - // ReSharper disable once UnthrowableException - options.ModuleName ??= _codeGenerator.Module.Name ?? throw new NullReferenceException("Name needs to be set"); + options.ModuleName ??= _codeGenerator.Module.Name ?? throw new InternalCompilerException("Name needs to be set"); options.SolutionPath ??= BuildOutputPath(options); options.OutputPath ??= Environment.CurrentDirectory; return options; diff --git a/Oberon0.Generator.MsilBin/Oberon0.Generator.MsilBin.csproj b/Oberon0.Generator.MsilBin/Oberon0.Generator.MsilBin.csproj index 284c0fb..3556dce 100644 --- a/Oberon0.Generator.MsilBin/Oberon0.Generator.MsilBin.csproj +++ b/Oberon0.Generator.MsilBin/Oberon0.Generator.MsilBin.csproj @@ -10,7 +10,6 @@ - diff --git a/Oberon0.System.Tests/Oberon0.System.Tests.csproj b/Oberon0.System.Tests/Oberon0.System.Tests.csproj new file mode 100644 index 0000000..7785d91 --- /dev/null +++ b/Oberon0.System.Tests/Oberon0.System.Tests.csproj @@ -0,0 +1,33 @@ + + + + net8.0 + enable + enable + + false + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/Oberon0.System.Tests/Oberon0System.MathTests.cs b/Oberon0.System.Tests/Oberon0System.MathTests.cs new file mode 100644 index 0000000..7bdcd56 --- /dev/null +++ b/Oberon0.System.Tests/Oberon0System.MathTests.cs @@ -0,0 +1,20 @@ +namespace Oberon0.System.Tests; + +using Oberon0System; +using Xunit; + +public static partial class Oberon0SystemTests +{ + [Theory] + [InlineData(0.0, false)] + [InlineData(double.NegativeInfinity, true)] + [InlineData(double.PositiveInfinity, true)] + public static void CanCallIsInfinity(double value, bool expected) + { + // Act + bool result = Oberon0System.IsInfinity(value); + + // Assert + Assert.Equal(expected, result); + } +} \ No newline at end of file diff --git a/Oberon0.System.Tests/Oberon0System.StringsTests.cs b/Oberon0.System.Tests/Oberon0System.StringsTests.cs new file mode 100644 index 0000000..84b75de --- /dev/null +++ b/Oberon0.System.Tests/Oberon0System.StringsTests.cs @@ -0,0 +1,77 @@ +using System.Globalization; + +namespace Oberon0.System.Tests; + +using Oberon0System; +using Xunit; + +public static partial class Oberon0SystemTests +{ + [Theory] + [InlineData(0)] + [InlineData(int.MaxValue)] + [InlineData(int.MinValue)] + [InlineData(5274097)] + [InlineData(-771204301)] + public static void CanCallConvertToStringWithInt(int value) + { + // Act + string result = Oberon0System.ConvertToString(value); + + // Assert + Assert.Equal(value.ToString(), result); + } + + [Theory] + [InlineData(0.0)] + [InlineData(1.0)] + [InlineData(double.MinValue)] + [InlineData(5274097.9283)] + [InlineData(-771204301.211)] + public static void CanCallConvertToStringWithReal(double value) + { + // Act + string result = Oberon0System.ConvertToString(value); + + // Assert + Assert.Equal(value.ToString(CultureInfo.InvariantCulture), result); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void CanCallConvertToStringWithBoolean(bool value) + { + // Act + string result = Oberon0System.ConvertToString(value); + + // Assert + Assert.Equal(value.ToString(), result); + } + + + [Theory] + [InlineData(0.0, "0", "G")] + [InlineData(1.0, "1", "G")] + [InlineData(10000000000000.00001, "10000000000000", "G")] + [InlineData(0.1, "0.1", "G")] + [InlineData(2.3E-06, "2.3E-06", "G")] + [InlineData(2.3E06, "2300000", "G")] + [InlineData(double.MinValue, "-1.7976931348623157E+308", "G")] + public static void TestToStringRealFormat(double value, string expected, string format) + { + string result = Oberon0System.ConvertToString(value, format); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(false, "false", "true", "false")] + [InlineData(true, "true", "true", "false")] + [InlineData(false, "0", "1", "0")] + [InlineData(true, "1", "1", "0")] + public static void TestToStringBooleanFormat(bool value, string expected, string trueVal, string falseVal) + { + string result = Oberon0System.ConvertToString(value, trueVal, falseVal); + Assert.Equal(expected, result); + } +} \ No newline at end of file diff --git a/Oberon0.System/Oberon0System.Strings.cs b/Oberon0.System/Oberon0System.Strings.cs index 67ab02e..b37fdae 100644 --- a/Oberon0.System/Oberon0System.Strings.cs +++ b/Oberon0.System/Oberon0System.Strings.cs @@ -8,56 +8,82 @@ using System.Globalization; using Oberon0System.Attributes; -namespace Oberon0System +namespace Oberon0System; + +public static partial class Oberon0System { - public static partial class Oberon0System + /// + /// Convert INTEGER to STRING. + /// + /// the value to convert + /// the converted string + [Oberon0Export("ToString", "STRING", "INTEGER")] + // ReSharper disable once UnusedMember.Global + public static string ConvertToString(int value) + { + return value.ToString(CultureInfo.InvariantCulture); + } + + /// + /// Convert REAL to STRING. + /// + /// the value to convert + /// the converted string + [Oberon0Export("ToString", "STRING", "REAL")] + // ReSharper disable once UnusedMember.Global + public static string ConvertToString(double value) + { + return value.ToString(CultureInfo.InvariantCulture); + } + + /// + /// Convert BOOLEAN to STRING. + /// + /// the value to convert + /// the converted string + [Oberon0Export("ToString", "STRING", "BOOLEAN")] + // ReSharper disable once UnusedMember.Global + public static string ConvertToString(bool value) + { + return value.ToString(CultureInfo.InvariantCulture); + } + + /// + /// Convert REAL to STRING. + /// + /// the value to convert + /// The format according to https://learn.microsoft.com/en-us/dotnet/api/system.double.tostring?view=net-8.0 + /// the converted string + [Oberon0Export("ToString", "STRING", "REAL", "STRING")] + // ReSharper disable once UnusedMember.Global + public static string ConvertToString(double value, string format) + { + return value.ToString(format, CultureInfo.InvariantCulture); + } + + /// + /// Convert REAL to STRING. + /// + /// the value to convert + /// value to be returned if value is true + /// value to be returned if value is false + /// the converted string + [Oberon0Export("ToString", "STRING", "BOOLEAN", "STRING", "STRING")] + // ReSharper disable once UnusedMember.Global + public static string ConvertToString(bool value, string trueValue, string falseValue) + { + return value ? trueValue : falseValue; + } + + /// + /// Get string length + /// + /// the value to convert + /// the converted string + [Oberon0Export("Length", "INTEGER", "STRING")] + // ReSharper disable once UnusedMember.Global + public static int Length(string value) { - /// - /// Convert INTEGER to STRING. - /// - /// the value to convert - /// the converted string - [Oberon0Export("ToString", "STRING", "INTEGER")] - // ReSharper disable once UnusedMember.Global - public static string ConvertToString(int value) - { - return value.ToString(CultureInfo.InvariantCulture); - } - - /// - /// Convert REAL to STRING. - /// - /// the value to convert - /// the converted string - [Oberon0Export("ToString", "STRING", "REAL")] - // ReSharper disable once UnusedMember.Global - public static string ConvertToString(double value) - { - return value.ToString(CultureInfo.InvariantCulture); - } - - /// - /// Convert BOOLEAN to STRING. - /// - /// the value to convert - /// the converted string - [Oberon0Export("ToString", "STRING", "BOOLEAN")] - // ReSharper disable once UnusedMember.Global - public static string ConvertToString(bool value) - { - return value.ToString(CultureInfo.InvariantCulture); - } - - /// - /// Get string length - /// - /// the value to convert - /// the converted string - [Oberon0Export("Length", "INTEGER", "STRING")] - // ReSharper disable once UnusedMember.Global - public static int Length(string value) - { - return value.Length; - } + return value.Length; } -} +} \ No newline at end of file diff --git a/UnitTestProject1/Types/StringTests.cs b/UnitTestProject1/Types/StringTests.cs index 7a7095a..feb332c 100644 --- a/UnitTestProject1/Types/StringTests.cs +++ b/UnitTestProject1/Types/StringTests.cs @@ -341,6 +341,26 @@ END test. Assert.IsType(expression.RightHandSide); } + [Fact] + public void TestStringMultStringInt() + { + var m = TestHelper.CompileString( + """ + + MODULE test; + VAR + r: STRING; + BEGIN + r := 'Hello' * 5; + END test. + """, + testOutput); + Assert.NotNull(m); + var s = Assert.IsAssignableFrom(m.Block.Statements[0]); + var stringExpression = Assert.IsType(s.Expression); + Assert.Equal("HelloHelloHelloHelloHello", stringExpression.Value); + } + [Fact] public void TestStringMultStringVar() { diff --git a/oberon0.sln b/oberon0.sln index d6179c2..118e563 100644 --- a/oberon0.sln +++ b/oberon0.sln @@ -38,6 +38,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oberon0.Generator.MsilBin.T EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oberon0.Shared", "Oberon0.Shared\Oberon0.Shared.csproj", "{C41EE89A-94E2-44D3-B75E-083A2980814B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Oberon0.System.Tests", "Oberon0.System.Tests\Oberon0.System.Tests.csproj", "{94639D79-9257-47CA-8162-C66827422E60}" + ProjectSection(ProjectDependencies) = postProject + {C2BE2EE3-1CA6-49E9-9CB0-A23429EBB027} = {C2BE2EE3-1CA6-49E9-9CB0-A23429EBB027} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -178,6 +183,22 @@ Global {C41EE89A-94E2-44D3-B75E-083A2980814B}.Release|x64.Build.0 = Release|Any CPU {C41EE89A-94E2-44D3-B75E-083A2980814B}.Release|x86.ActiveCfg = Release|Any CPU {C41EE89A-94E2-44D3-B75E-083A2980814B}.Release|x86.Build.0 = Release|Any CPU + {94639D79-9257-47CA-8162-C66827422E60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94639D79-9257-47CA-8162-C66827422E60}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94639D79-9257-47CA-8162-C66827422E60}.Debug|ARM.ActiveCfg = Debug|Any CPU + {94639D79-9257-47CA-8162-C66827422E60}.Debug|ARM.Build.0 = Debug|Any CPU + {94639D79-9257-47CA-8162-C66827422E60}.Debug|x64.ActiveCfg = Debug|Any CPU + {94639D79-9257-47CA-8162-C66827422E60}.Debug|x64.Build.0 = Debug|Any CPU + {94639D79-9257-47CA-8162-C66827422E60}.Debug|x86.ActiveCfg = Debug|Any CPU + {94639D79-9257-47CA-8162-C66827422E60}.Debug|x86.Build.0 = Debug|Any CPU + {94639D79-9257-47CA-8162-C66827422E60}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94639D79-9257-47CA-8162-C66827422E60}.Release|Any CPU.Build.0 = Release|Any CPU + {94639D79-9257-47CA-8162-C66827422E60}.Release|ARM.ActiveCfg = Release|Any CPU + {94639D79-9257-47CA-8162-C66827422E60}.Release|ARM.Build.0 = Release|Any CPU + {94639D79-9257-47CA-8162-C66827422E60}.Release|x64.ActiveCfg = Release|Any CPU + {94639D79-9257-47CA-8162-C66827422E60}.Release|x64.Build.0 = Release|Any CPU + {94639D79-9257-47CA-8162-C66827422E60}.Release|x86.ActiveCfg = Release|Any CPU + {94639D79-9257-47CA-8162-C66827422E60}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -189,6 +210,7 @@ Global {0B0B57CB-CEDB-4DA7-9DEF-05572E2C0D63} = {7347FBB5-C0DF-45D3-A436-50D9D06ED52F} {2C232005-6F58-4E0C-B3E7-020E5CE3CF24} = {7347FBB5-C0DF-45D3-A436-50D9D06ED52F} {C41EE89A-94E2-44D3-B75E-083A2980814B} = {6485043F-55F6-48B1-AC01-C178B0F2C167} + {94639D79-9257-47CA-8162-C66827422E60} = {6485043F-55F6-48B1-AC01-C178B0F2C167} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {20CCD7D0-3C73-44E1-9D01-3D6487B302B1} diff --git a/oberon0/Expressions/Operations/Internal/ArithmeticOperationAttribute.cs b/oberon0/Expressions/Operations/Internal/ArithmeticOperationAttribute.cs index 4643f38..184f942 100644 --- a/oberon0/Expressions/Operations/Internal/ArithmeticOperationAttribute.cs +++ b/oberon0/Expressions/Operations/Internal/ArithmeticOperationAttribute.cs @@ -7,7 +7,7 @@ using System; using System.Composition; -using JetBrains.Annotations; +using System.Diagnostics.CodeAnalysis; using Oberon0.Compiler.Types; namespace Oberon0.Compiler.Expressions.Operations.Internal @@ -21,12 +21,22 @@ public class ArithmeticOperationAttribute( BaseTypes resultType) : ExportAttribute(typeof(IArithmeticOperation)), IArithmeticOpMetadata { - [UsedImplicitly] public BaseTypes LeftHandType { get; set; } = leftHandType; + // ReSharper disable once UnusedMember.Global + public BaseTypes LeftHandType { get; set; } = leftHandType; - [UsedImplicitly] public BaseTypes RightHandType { get; set; } = rightHandType; + // ReSharper disable once UnusedMember.Global + public BaseTypes RightHandType { get; set; } = rightHandType; - public int Operation { get; set; } = operation; + public int Operation { + get; + [ExcludeFromCodeCoverage(Justification = "Called by MEF")] + set; + } = operation; - public BaseTypes ResultType { get; set; } = resultType; + public BaseTypes ResultType { + get; + [ExcludeFromCodeCoverage(Justification = "Called by MEF")] + set; + } = resultType; } } diff --git a/oberon0/Expressions/Operations/OpRelop.cs b/oberon0/Expressions/Operations/OpRelop.cs index e91def2..844d4a5 100644 --- a/oberon0/Expressions/Operations/OpRelop.cs +++ b/oberon0/Expressions/Operations/OpRelop.cs @@ -8,6 +8,7 @@ using System; using JetBrains.Annotations; using Oberon0.Compiler.Definitions; +using Oberon0.Compiler.Exceptions; using Oberon0.Compiler.Expressions.Constant; using Oberon0.Compiler.Expressions.Operations.Internal; using Oberon0.Compiler.Types; @@ -72,17 +73,13 @@ private static bool HandleBoolRelop( ConstantExpression left, ConstantExpression right) { - if (operationParameters.Operation == OberonGrammarLexer.EQUAL) + return operationParameters.Operation switch { - return left.ToBool() == right.ToBool(); - } - - if (operationParameters.Operation == OberonGrammarLexer.NOTEQUAL) - { - return left.ToBool() != right.ToBool(); - } - - return false; + OberonGrammarLexer.EQUAL => left.ToBool() == right.ToBool(), + OberonGrammarLexer.NOTEQUAL => left.ToBool() != right.ToBool(), + _ => throw new InternalCompilerException( + $"Unknown comparison{OberonGrammarLexer.ruleNames[operationParameters.Operation]}") + }; } private static bool HandleStandardRelop( @@ -98,7 +95,8 @@ private static bool HandleStandardRelop( OberonGrammarLexer.LE => left.ToDouble() <= right.ToDouble(), OberonGrammarLexer.NOTEQUAL => Math.Abs(left.ToDouble() - right.ToDouble()) > double.Epsilon, OberonGrammarLexer.EQUAL => Math.Abs(left.ToDouble() - right.ToDouble()) < double.Epsilon, - _ => throw new InvalidOperationException("Unknown comparison") + _ => throw new InternalCompilerException( + $"Unknown comparison{OberonGrammarLexer.ruleNames[operationParameters.Operation]}") }; return res;