From 94e672ab4c3595d756da3d411876cd59cdb823a4 Mon Sep 17 00:00:00 2001 From: Per Lundberg Date: Thu, 1 Jun 2023 22:20:51 +0300 Subject: [PATCH] (compiler) Add first steps towards experimental compiler --- Perlang.sln | 7 + docs/examples/quickstart/hello_world.per | 3 +- global.ruleset | 1 + release-notes/v0.3.0.md | 1 + release-notes/v0.4.0.md | 2 + .../NotImplementedInCompiledModeException.cs | 15 + .../Compiler/PerlangCompilerException.cs | 11 + src/Perlang.Common/Expr.cs | 7 + src/Perlang.Common/ITypeReference.cs | 38 + src/Perlang.Common/PerlangMode.cs | 25 + src/Perlang.Common/Stmt.cs | 9 +- src/Perlang.Common/TypeReference.cs | 24 + src/Perlang.Compiler/Perlang.Compiler.csproj | 24 + .../Properties/AssemblyInfo.cs | 5 + .../Perlang.ConsoleApp.csproj | 1 + src/Perlang.ConsoleApp/Program.cs | 37 +- .../Compiler/CompilerMessages.cs | 22 + .../Compiler/PerlangCompiler.cs | 1177 +++++++++++++++++ src/Perlang.Interpreter/PerlangInterpreter.cs | 121 +- .../Typing/TypeValidator.cs | 2 +- src/Perlang.Parser/PerlangParser.cs | 81 ++ src/Perlang.Parser/Properties/AssemblyInfo.cs | 7 + src/Perlang.Parser/ScanAndParseResult.cs | 43 + src/Perlang.Stdlib/Internal/Utils.cs | 1 + .../Classes/ClassesTests.cs | 10 +- .../Comments/CommentsTests.cs | 11 +- src/Perlang.Tests.Integration/EvalHelper.cs | 112 +- src/Perlang.Tests.Integration/EvalResult.cs | 6 + src/Perlang.Tests.Integration/For/Syntax.cs | 6 +- .../Function/Return.cs | 3 + .../IndexOperator/DictionaryIndexing.cs | 4 +- .../AmbiguousCombinationOfOperators.cs | 5 +- .../LogicalOperator/And.cs | 19 +- .../LogicalOperator/Or.cs | 19 +- .../Number/NumberTests.cs | 40 +- .../Binary/AdditionAssignmentTests.cs | 6 +- .../Operator/Binary/AdditionTests.cs | 22 +- .../Operator/Binary/BinaryOperatorData.cs | 580 ++++---- .../Binary/BinaryOperatorDataTests.cs | 62 +- .../Operator/Binary/Comparison.cs | 171 ++- .../Operator/Binary/DivisionTests.cs | 8 +- .../Operator/Binary/EqualTests.cs | 15 +- .../Operator/Binary/ExponentialTests.cs | 32 +- .../Operator/Binary/GreaterEqualTests.cs | 5 +- .../Operator/Binary/GreaterTests.cs | 5 +- .../Operator/Binary/LessEqualTests.cs | 5 +- .../Operator/Binary/LessTests.cs | 5 +- .../Operator/Binary/ModuloTests.cs | 12 +- .../Operator/Binary/MultiplicationTests.cs | 6 +- .../Operator/Binary/NotEqualTests.cs | 16 +- .../Operator/Binary/ShiftLeftTests.cs | 6 +- .../Operator/Binary/ShiftRightTests.cs | 8 +- .../Binary/SubtractionAssignmentTests.cs | 6 +- .../Operator/Binary/SubtractionTests.cs | 6 +- .../Operator/PostfixDecrement.cs | 2 +- .../Operator/PostfixIncrement.cs | 4 +- .../Operator/PrefixNegation.cs | 30 + .../Perlang.Tests.Integration.csproj | 1 + src/Perlang.Tests.Integration/Precedence.cs | 20 +- src/Perlang.Tests.Integration/Return.cs | 6 +- .../Stdlib/ArgvTests.cs | 10 +- .../Stdlib/Base64DecodeTests.cs | 6 +- .../Stdlib/Base64EncodeTests.cs | 29 +- .../Stdlib/LibcTests.cs | 12 +- .../Stdlib/PosixTests.cs | 10 +- .../Stdlib/TimeTests.cs | 25 +- .../Typing/BigintTests.cs | 14 +- .../Typing/DoubleTests.cs | 6 +- .../Typing/FloatTests.cs | 6 +- .../Typing/IntTests.cs | 4 +- .../Typing/LongTests.cs | 6 +- .../Typing/StringTests.cs | 2 +- .../Typing/TypingTests.cs | 8 +- .../Typing/UintTests.cs | 6 +- .../Resolution/NameResolverTest.cs | 2 +- .../Interpreter/Typing/TypeResolverTest.cs | 2 +- 76 files changed, 2365 insertions(+), 721 deletions(-) create mode 100644 src/Perlang.Common/Compiler/NotImplementedInCompiledModeException.cs create mode 100644 src/Perlang.Common/Compiler/PerlangCompilerException.cs create mode 100644 src/Perlang.Common/PerlangMode.cs create mode 100644 src/Perlang.Compiler/Perlang.Compiler.csproj create mode 100644 src/Perlang.Compiler/Properties/AssemblyInfo.cs create mode 100644 src/Perlang.Interpreter/Compiler/CompilerMessages.cs create mode 100644 src/Perlang.Interpreter/Compiler/PerlangCompiler.cs create mode 100644 src/Perlang.Parser/ScanAndParseResult.cs diff --git a/Perlang.sln b/Perlang.sln index 56b2b91d..ea83ee24 100644 --- a/Perlang.sln +++ b/Perlang.sln @@ -27,6 +27,8 @@ EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perlang.Tests.Architecture", "src\Perlang.Tests.Architecture\Perlang.Tests.Architecture.csproj", "{E9D632CB-E520-4868-9E64-A4A70ACD713C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perlang.Compiler", "src\Perlang.Compiler\Perlang.Compiler.csproj", "{7E7CF7BF-F792-49C9-BB16-67FE856D8D51}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -65,6 +67,10 @@ Global {E9D632CB-E520-4868-9E64-A4A70ACD713C}.Debug|Any CPU.Build.0 = Debug|Any CPU {E9D632CB-E520-4868-9E64-A4A70ACD713C}.Release|Any CPU.ActiveCfg = Release|Any CPU {E9D632CB-E520-4868-9E64-A4A70ACD713C}.Release|Any CPU.Build.0 = Release|Any CPU + {7E7CF7BF-F792-49C9-BB16-67FE856D8D51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7E7CF7BF-F792-49C9-BB16-67FE856D8D51}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7E7CF7BF-F792-49C9-BB16-67FE856D8D51}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7E7CF7BF-F792-49C9-BB16-67FE856D8D51}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {B338D29B-A85D-4650-9BFF-FBFE55258C58} = {B924E83B-6AFE-4ABF-B79D-444F92582892} @@ -75,5 +81,6 @@ Global {CD78DA11-CB31-4892-8F96-3B7FD7015B83} = {F13F8E4E-5231-4321-9722-1BFCC08E5BB5} {1874E267-5D6F-4EA4-8938-F2A0D19F27B1} = {F13F8E4E-5231-4321-9722-1BFCC08E5BB5} {E9D632CB-E520-4868-9E64-A4A70ACD713C} = {F13F8E4E-5231-4321-9722-1BFCC08E5BB5} + {7E7CF7BF-F792-49C9-BB16-67FE856D8D51} = {B924E83B-6AFE-4ABF-B79D-444F92582892} EndGlobalSection EndGlobal diff --git a/docs/examples/quickstart/hello_world.per b/docs/examples/quickstart/hello_world.per index e1f61251..144147b9 100644 --- a/docs/examples/quickstart/hello_world.per +++ b/docs/examples/quickstart/hello_world.per @@ -1,2 +1 @@ -// hello_world.per -print "Hello World"; +print "foobar"[0]; diff --git a/global.ruleset b/global.ruleset index e48b963a..b6d441e7 100644 --- a/global.ruleset +++ b/global.ruleset @@ -36,6 +36,7 @@ + diff --git a/release-notes/v0.3.0.md b/release-notes/v0.3.0.md index d7864c4f..d3efa5f4 100644 --- a/release-notes/v0.3.0.md +++ b/release-notes/v0.3.0.md @@ -3,6 +3,7 @@ - The REPL is also noticeably nicer to use now, particularly for those of us used to bash/Emacs-like keybindings. [[#354][354]], [[#356][356]] - API docs on the website was broken since the v0.2.0 release for unknown reasons. [[#344][344]] has more details. - Based on community feedback, the project README was also slightly improved. [[#371][371]] +- All changes below were made by @perlun ### Added #### Data types diff --git a/release-notes/v0.4.0.md b/release-notes/v0.4.0.md index 9059dd71..0241490b 100644 --- a/release-notes/v0.4.0.md +++ b/release-notes/v0.4.0.md @@ -1,5 +1,7 @@ ## [0.4.0] - Unreleased - The fourth public release of Perlang. +- For more details on how to install Perlang on your own machine, see https://perlang.org/download/. +- All changes below were made by @perlun ### Added #### Experimental compilation diff --git a/src/Perlang.Common/Compiler/NotImplementedInCompiledModeException.cs b/src/Perlang.Common/Compiler/NotImplementedInCompiledModeException.cs new file mode 100644 index 00000000..cce10ddb --- /dev/null +++ b/src/Perlang.Common/Compiler/NotImplementedInCompiledModeException.cs @@ -0,0 +1,15 @@ +namespace Perlang.Compiler; + +/// +/// Exception thrown for valid Perlang code, which is currently "not yet supported" in compiled mode. +/// +/// This exception is thrown to make it possible for integration tests to skip tests for code which is known to not +/// yet work. +/// +public class NotImplementedInCompiledModeException : PerlangCompilerException +{ + public NotImplementedInCompiledModeException(string message) + : base(message) + { + } +} diff --git a/src/Perlang.Common/Compiler/PerlangCompilerException.cs b/src/Perlang.Common/Compiler/PerlangCompilerException.cs new file mode 100644 index 00000000..329dec8a --- /dev/null +++ b/src/Perlang.Common/Compiler/PerlangCompilerException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Perlang.Compiler; + +public class PerlangCompilerException : Exception +{ + public PerlangCompilerException(string message) + : base(message) + { + } +} diff --git a/src/Perlang.Common/Expr.cs b/src/Perlang.Common/Expr.cs index a2b41882..624038f8 100644 --- a/src/Perlang.Common/Expr.cs +++ b/src/Perlang.Common/Expr.cs @@ -344,7 +344,14 @@ public override string ToString() => public class Get : Expr, ITokenAware { + /// + /// Gets the target object whose field/property/method is being accessed. + /// public Expr Object { get; } + + /// + /// Gets the name of the field/property/method that is being accessed. + /// public Token Name { get; } // TODO: Would be much nicer to have this be without setter, but there is no easy way to accomplish this, diff --git a/src/Perlang.Common/ITypeReference.cs b/src/Perlang.Common/ITypeReference.cs index 2d9669a5..b74a7de5 100644 --- a/src/Perlang.Common/ITypeReference.cs +++ b/src/Perlang.Common/ITypeReference.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Numerics; namespace Perlang { @@ -8,9 +9,17 @@ public interface ITypeReference { Token? TypeSpecifier { get; } + /// + /// Gets or sets the CLR/.NET type that this refers to. + /// // TODO: Remove setter to make this interface and class be fully immutable, for debuggability. Type? ClrType { get; set; } + /// + /// Gets the C++ type that this refers to. + /// + string CppType { get; } + /// /// Gets a value indicating whether the type reference contains an explicit type specifier or not. If this is /// false, the user is perhaps intending for the type to be inferred from the program context. @@ -27,5 +36,34 @@ public interface ITypeReference /// Gets a value indicating whether this type reference refers to a `null` value. /// public bool IsNullObject => ClrType == typeof(NullObject); + + bool IsValidNumberType => + ClrType switch + { + null => false, + var t when t == typeof(SByte) => true, + var t when t == typeof(Int16) => true, + var t when t == typeof(Int32) => true, + var t when t == typeof(Int64) => true, + var t when t == typeof(Byte) => true, + var t when t == typeof(UInt16) => true, + var t when t == typeof(UInt32) => true, + var t when t == typeof(UInt64) => true, + var t when t == typeof(Single) => true, // i.e. float + var t when t == typeof(Double) => true, + var t when t == typeof(BigInteger) => true, + _ => false + }; + + bool IsStringType() => + ClrType switch + { + null => false, + + // Cannot use typeof(AsciiString) since Perlang.Common cannot depend on Perlang.Stdlib + var t when t.FullName == "Perlang.Lang.AsciiString" => true, + + _ => false + }; } } diff --git a/src/Perlang.Common/PerlangMode.cs b/src/Perlang.Common/PerlangMode.cs new file mode 100644 index 00000000..99f34595 --- /dev/null +++ b/src/Perlang.Common/PerlangMode.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; + +namespace Perlang; + +public static class PerlangMode +{ + public static bool ExperimentalCompilation + { + get + { + string environmentVariable = Environment.GetEnvironmentVariable("PERLANG_EXPERIMENTAL_COMPILATION"); + + // The environment variable takes precedence, if set + if (Boolean.TryParse(environmentVariable, out bool flag)) + { + return flag; + } + + string homeDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + + return File.Exists(Path.Combine(homeDirectory, ".perlang_experimental_compilation")); + } + } +} diff --git a/src/Perlang.Common/Stmt.cs b/src/Perlang.Common/Stmt.cs index 394abb6c..f0dea9b1 100644 --- a/src/Perlang.Common/Stmt.cs +++ b/src/Perlang.Common/Stmt.cs @@ -1,3 +1,4 @@ +#nullable enable using System.Collections.Generic; using System.Collections.Immutable; @@ -99,9 +100,9 @@ public class If : Stmt { public Expr Condition { get; } public Stmt ThenBranch { get; } - public Stmt ElseBranch { get; } + public Stmt? ElseBranch { get; } - public If(Expr condition, Stmt thenBranch, Stmt elseBranch) + public If(Expr condition, Stmt thenBranch, Stmt? elseBranch) { Condition = condition; ThenBranch = thenBranch; @@ -132,9 +133,9 @@ public override TR Accept(IVisitor visitor) public class Return : Stmt { public Token Keyword { get; } - public Expr Value { get; } + public Expr? Value { get; } - public Return(Token keyword, Expr value) + public Return(Token keyword, Expr? value) { Keyword = keyword; Value = value; diff --git a/src/Perlang.Common/TypeReference.cs b/src/Perlang.Common/TypeReference.cs index 349564d7..9f345c65 100644 --- a/src/Perlang.Common/TypeReference.cs +++ b/src/Perlang.Common/TypeReference.cs @@ -1,5 +1,7 @@ #nullable enable using System; +using System.Numerics; +using Perlang.Compiler; namespace Perlang { @@ -44,6 +46,28 @@ public Type? ClrType } } + public string CppType => + clrType switch + { + // TODO: Add the other Perlang-supported types as well + var t when t == typeof(Int32) => "int32_t", + var t when t == typeof(UInt32) => "uint32_t", + var t when t == typeof(Int64) => "int64_t", + var t when t == typeof(UInt64) => "uint64_t", + var t when t == typeof(Single) => "float", + var t when t == typeof(Double) => "double", + var t when t == typeof(bool) => "bool", + var t when t == typeof(void) => "void", + var t when t == typeof(BigInteger) => throw new NotImplementedInCompiledModeException("BigInteger is not yet supported in compiled mode"), + null => throw new InvalidOperationException($"Internal error: ClrType was unexpectedly null"), + + // TODO: Differentiate from these on the C++ level as well + var t when t.FullName == "Perlang.Lang.AsciiString" => "const char *", + var t when t.FullName == "Perlang.Lang.String" => "const char *", + + _ => throw new NotImplementedInCompiledModeException($"Internal error: C++ type for {clrType} not defined") + }; + /// /// Initializes a new instance of the class, for a given type specifier. The type /// specifier can be null, in which case type inference will be attempted. diff --git a/src/Perlang.Compiler/Perlang.Compiler.csproj b/src/Perlang.Compiler/Perlang.Compiler.csproj new file mode 100644 index 00000000..feefdaef --- /dev/null +++ b/src/Perlang.Compiler/Perlang.Compiler.csproj @@ -0,0 +1,24 @@ + + + + net7.0 + false + enable + + + + bin\Debug\net7.0\Perlang.Compiler.xml + 1591 + + + + bin\Release\net7.0\Perlang.Compiler.xml + 1591 + + + + + + + + diff --git a/src/Perlang.Compiler/Properties/AssemblyInfo.cs b/src/Perlang.Compiler/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..5fc436cd --- /dev/null +++ b/src/Perlang.Compiler/Properties/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System.Reflection; +using Perlang; + +[assembly: AssemblyVersion(CommonConstants.Version)] +[assembly: AssemblyInformationalVersion(CommonConstants.InformationalVersion)] diff --git a/src/Perlang.ConsoleApp/Perlang.ConsoleApp.csproj b/src/Perlang.ConsoleApp/Perlang.ConsoleApp.csproj index 4f008742..48d737fa 100644 --- a/src/Perlang.ConsoleApp/Perlang.ConsoleApp.csproj +++ b/src/Perlang.ConsoleApp/Perlang.ConsoleApp.csproj @@ -35,6 +35,7 @@ + diff --git a/src/Perlang.ConsoleApp/Program.cs b/src/Perlang.ConsoleApp/Program.cs index 41c4d3df..a529cbb8 100644 --- a/src/Perlang.ConsoleApp/Program.cs +++ b/src/Perlang.ConsoleApp/Program.cs @@ -13,6 +13,7 @@ using Mono.Terminal; using Perlang.Internal; using Perlang.Interpreter; +using Perlang.Interpreter.Compiler; using Perlang.Interpreter.NameResolution; using Perlang.Parser; using ParseError = Perlang.Parser.ParseError; @@ -64,6 +65,7 @@ internal enum ExitCodes } private readonly PerlangInterpreter interpreter; + private readonly PerlangCompiler compiler; /// /// Writes a Perlang native string to the standard output stream. @@ -87,6 +89,11 @@ internal enum ExitCodes private readonly HashSet disabledWarningsAsErrors; + /// + /// A flag which determines if (highly experimental) compilation to machine code is enabled or not. + /// + private readonly bool experimentalCompilation; + private bool hadError; private bool hadRuntimeError; @@ -234,7 +241,8 @@ public static int MainWithCustomConsole(string[] args, IPerlangConsole console) var program = new Program( replMode: false, standardOutputHandler: console.WriteStdoutLine, - disabledWarningsAsErrors: disabledWarningsAsErrorsList + disabledWarningsAsErrors: disabledWarningsAsErrorsList, + experimentalCompilation: PerlangMode.ExperimentalCompilation ); result = program.RunFile(scriptName); @@ -247,7 +255,8 @@ public static int MainWithCustomConsole(string[] args, IPerlangConsole console) replMode: false, arguments: remainingArguments, standardOutputHandler: console.WriteStdoutLine, - disabledWarningsAsErrors: disabledWarningsAsErrorsList + disabledWarningsAsErrors: disabledWarningsAsErrorsList, + experimentalCompilation: PerlangMode.ExperimentalCompilation ); result = program.RunFile(scriptName); @@ -290,13 +299,15 @@ internal Program( Action standardOutputHandler, IEnumerable? arguments = null, IEnumerable? disabledWarningsAsErrors = null, - Action? runtimeErrorHandler = null) + Action? runtimeErrorHandler = null, + bool experimentalCompilation = false) { // TODO: Make these be separate handlers at some point, so the caller can separate between these types of // TODO: output. this.standardOutputHandler = standardOutputHandler; this.standardErrorHandler = standardOutputHandler; this.disabledWarningsAsErrors = (disabledWarningsAsErrors ?? Enumerable.Empty()).ToHashSet(); + this.experimentalCompilation = experimentalCompilation; // Convenience fields while we are migrating away from CLR strings to Perlang strings. this.standardOutputHandlerFromClrString = s => this.standardOutputHandler(Lang.String.from(s)); @@ -309,6 +320,11 @@ internal Program( arguments ?? new List(), replMode: replMode ); + + compiler = new PerlangCompiler( + runtimeErrorHandler ?? RuntimeError, + this.standardOutputHandler + ); } private int RunFile(string path) @@ -320,8 +336,16 @@ private int RunFile(string path) } var bytes = File.ReadAllBytes(path); + string source = Encoding.UTF8.GetString(bytes); - Run(Encoding.UTF8.GetString(bytes), CompilerWarning); + if (experimentalCompilation) + { + CompileAndRun(source, path, CompilerWarning); + } + else + { + Run(source, CompilerWarning); + } // Indicate an error in the exit code. if (hadError) @@ -426,6 +450,11 @@ internal int Run(string source, CompilerWarningHandler compilerWarningHandler) return (int)ExitCodes.SUCCESS; } + private void CompileAndRun(string source, string path, CompilerWarningHandler compilerWarningHandler) + { + compiler.CompileAndRun(source, path, ScanError, ParseError, NameResolutionError, ValidationError, ValidationError, compilerWarningHandler); + } + private void ParseAndPrint(string source) { string? result = interpreter.Parse(source, ScanError, ParseError); diff --git a/src/Perlang.Interpreter/Compiler/CompilerMessages.cs b/src/Perlang.Interpreter/Compiler/CompilerMessages.cs new file mode 100644 index 00000000..6c3b5b4b --- /dev/null +++ b/src/Perlang.Interpreter/Compiler/CompilerMessages.cs @@ -0,0 +1,22 @@ +using Perlang.Internal.Extensions; +using Perlang.Interpreter.Extensions; + +namespace Perlang.Interpreter.Compiler; + +public static class CompilerMessages +{ + public static string UnsupportedOperatorTypeInBinaryExpression(TokenType operatorType) => + $"Internal error: Unsupported operator {operatorType.ToSourceString()} in binary expression."; + + public static string UnsupportedOperandsInBinaryExpression(TokenType operatorType, ITypeReference leftTypeReference, ITypeReference rightTypeReference) => + $"Internal error: Unsupported combination of operands to {operatorType.ToSourceString()} operator: {leftTypeReference.ClrType.ToTypeKeyword()} and {rightTypeReference.ClrType.ToTypeKeyword()}."; + + public static string UnsupportedOperatorTypeInLogicalExpression(TokenType operatorType) => + $"Internal error: Unsupported operator {operatorType.ToSourceString()} in logical expression."; + + public static string UnsupportedOperatorTypeInUnaryPrefixExpression(TokenType operatorType) => + $"Internal error: Unsupported operator {operatorType.ToSourceString()} in unary prefix expression."; + + public static string UnsupportedOperatorTypeInUnaryPostfixExpression(TokenType operatorType) => + $"Internal error: Unsupported operator {operatorType.ToSourceString()} in unary postfix expression."; +} diff --git a/src/Perlang.Interpreter/Compiler/PerlangCompiler.cs b/src/Perlang.Interpreter/Compiler/PerlangCompiler.cs new file mode 100644 index 00000000..12efae8f --- /dev/null +++ b/src/Perlang.Interpreter/Compiler/PerlangCompiler.cs @@ -0,0 +1,1177 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Reflection; +using System.Text; +using Perlang.Attributes; +using Perlang.Compiler; +using Perlang.Exceptions; +using Perlang.Internal.Extensions; +using Perlang.Interpreter.CodeAnalysis; +using Perlang.Interpreter.Immutability; +using Perlang.Interpreter.Internals; +using Perlang.Interpreter.NameResolution; +using Perlang.Interpreter.Typing; +using Perlang.Lang; +using Perlang.Parser; +using Perlang.Stdlib; +using static Perlang.TokenType; +using String = System.String; + +namespace Perlang.Interpreter.Compiler; + +/// +/// Compiles Perlang source to executable form (typically ELF binaries on Linux, Mach-O on macOS and PE32/PE32+ on +/// Windows). +/// +/// The compilation takes place in two steps: +/// +/// - First, a machine-generated C++ file is generated. +/// - Then, the C++ file is piped to `clang` (version 14). `clang` generates an executable file. +/// +/// Note that the compilation is highly experimental at the moment and has only been tested on Linux. +/// +/// The generated C++ files follows the following design principles, in order of importance: +/// +/// - They are valid C++ +/// - They semantically correspond to the Perlang program they have been compiled from +/// - They are properly intended and formatted, as close to the "PL Style" for Perlang as possible. +/// +/// Particularly the formatting is a "best-effort" at this stage. Machine-generated code in general is unlikely to be as +/// well-formatted as human-formatted code, unless passed through a semantically aware post-processor for formatting. +/// This is not the case for this simple implementation; it simply knows the level of indentation at each stage in the +/// processing and does its best to try to produce something which is "not garbage", but not necessarily perfectly +/// aesthetically pleasing. +/// +public class PerlangCompiler : Expr.IVisitor, Stmt.IVisitor +{ + // To avoid unnecessary overhead, we cache the compiled output for a program by default. When working on changes to + // the actual Perlang codebase, this cache often needs to be disabled to test our changes. + private static bool CompilationCacheDisabled + { + get + { + string environmentVariable = Environment.GetEnvironmentVariable("PERLANG_EXPERIMENTAL_COMPILATION_CACHE_DISABLED") ?? String.Empty; + + if (Boolean.TryParse(environmentVariable, out bool flag)) + { + return flag; + } + + return false; + } + } + + private readonly Action runtimeErrorHandler; + private readonly Action standardOutputHandler; + private readonly PerlangEnvironment globals = new(); + private readonly IImmutableDictionary superGlobals; + + /// + /// A collection of all currently defined global classes (both native/.NET and classes defined in Perlang code.) + /// + private readonly IDictionary globalClasses = new Dictionary(); + + private readonly ImmutableDictionary nativeClasses; + private readonly IDictionary methods; + + private IBindingHandler BindingHandler { get; } + + private StringBuilder currentMethod; + + private int indentationLevel = 1; + + /// + /// Initializes a new instance of the class. + /// + /// A callback that will be called on runtime errors. Note that after calling + /// this handler, the interpreter will abort the script. + /// An callback that will receive output printed to standard output. + /// A binding handler, or `null` to let the interpreter create a new instance. + /// An optional list of runtime arguments. + public PerlangCompiler( + Action runtimeErrorHandler, + Action standardOutputHandler, + IBindingHandler? bindingHandler = null, + IEnumerable? arguments = null) + { + this.runtimeErrorHandler = runtimeErrorHandler; + this.standardOutputHandler = standardOutputHandler; + this.BindingHandler = bindingHandler ?? new BindingHandler(); + + this.currentMethod = new StringBuilder(); + this.methods = new Dictionary(); + + methods["main"] = new Method("main", ImmutableList.Create(), "int", currentMethod); + + var argumentsList = (arguments ?? Array.Empty()).ToImmutableList(); + + superGlobals = CreateSuperGlobals(argumentsList); + + LoadStdlib(); + nativeClasses = RegisterGlobalFunctionsAndClasses(); + } + + private IImmutableDictionary CreateSuperGlobals(ImmutableList argumentsList) + { + // Set up the super-global ARGV variable. + var result = new Dictionary + { + { "ARGV", typeof(Argv) } + }.ToImmutableDictionary(); + + // TODO: Returning a value AND modifying the globals like this feels like a code smell. Try to figure out + // TODO: a more sensible way. + globals.Define(new Token(VAR, "ARGV", null, -1), new Argv(argumentsList)); + + return result; + } + + private static void LoadStdlib() + { + // Because of implicit dependencies, this is not loaded automatically; we must manually load this + // assembly to ensure all Callables within it are registered in the global namespace. + Assembly.Load("Perlang.StdLib"); + } + + private ImmutableDictionary RegisterGlobalFunctionsAndClasses() + { + RegisterGlobalClasses(); + + // We need to make a copy of this at this early stage, when it _only_ contains native classes, so that + // we can feed it to the Resolver class. + return globalClasses.ToImmutableDictionary(kvp => kvp.Key, kvp => (Type)kvp.Value); + } + + /// + /// Registers global classes defined in native .NET code. + /// + /// Multiple classes with the same name was encountered. + private void RegisterGlobalClasses() + { + var globalClassesQueryable = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(a => a.GetTypes()) + .Select(t => new + { + Type = t, + ClassAttribute = t.GetCustomAttribute() + }) + .Where(t => t.ClassAttribute != null && ( + !t.ClassAttribute.Platforms.Any() || t.ClassAttribute.Platforms.Contains(Environment.OSVersion.Platform) + )); + + foreach (var globalClass in globalClassesQueryable) + { + string name = globalClass.ClassAttribute!.Name ?? globalClass.Type.Name; + + if (globals.Get(name) != null) + { + throw new PerlangCompilerException( + $"Attempted to define global class '{name}', but another identifier with the same name already exists" + ); + } + + globalClasses[name] = globalClass.Type; + } + } + + /// + /// Compiles the given Perlang program to an executable, and runs the executable afterwards. + /// + /// The Perlang program to compile. + /// The path to the source file (used for generating error messages). + /// A handler for scanner errors. + /// A handler for parse errors. + /// A handler for resolve errors. + /// A handler for type validation errors. + /// A handler for immutability validation errors. + /// A handler for compiler warnings. + /// The path to the compiled executable. Note that this will be non-null even on unsuccessful + /// compilation. + public string? CompileAndRun( + string source, + string path, + ScanErrorHandler scanErrorHandler, + ParseErrorHandler parseErrorHandler, + NameResolutionErrorHandler nameResolutionErrorHandler, + ValidationErrorHandler typeValidationErrorHandler, + ValidationErrorHandler immutabilityValidationErrorHandler, + CompilerWarningHandler compilerWarningHandler) + { + string? executablePath = Compile( + source, + path, + scanErrorHandler, + parseErrorHandler, + nameResolutionErrorHandler, + typeValidationErrorHandler, + immutabilityValidationErrorHandler, + compilerWarningHandler + ); + + if (executablePath == null) + { + // These errors have already been propagated to the caller; we can simply return a this point. + return null; + } + + Process? process = Process.Start(new ProcessStartInfo + { + FileName = executablePath, + RedirectStandardOutput = true, + RedirectStandardError = true, + }); + + if (process == null) + { + runtimeErrorHandler(new RuntimeError(null, $"Launching process for {path} failed")); + return executablePath; + } + + process.WaitForExit(); + + // Note that output is currently not streamed to the caller while the process is running. All stdout and stderr + // output is read after the process has exited. + while (process.StandardError.ReadLine() is { } standardOutputLine) + { + standardOutputHandler(Lang.String.from(standardOutputLine)); + } + + while (process.StandardOutput.ReadLine() is { } standardErrorLine) + { + standardOutputHandler(Lang.String.from(standardErrorLine)); + } + + if (process.ExitCode != 0) + { + runtimeErrorHandler(new RuntimeError(null, $"Process exited with exit code {process.ExitCode}")); + } + + return executablePath; + } + + /// + /// Compiles the given Perlang program to an executable. + /// + /// The Perlang program to compile. + /// The path to the source file (used for generating error messages). + /// A handler for scanner errors. + /// A handler for parse errors. + /// A handler for resolve errors. + /// A handler for type validation errors. + /// A handler for immutability validation errors. + /// A handler for compiler warnings. + /// The path to the generated executable file, or `null` if compilation failed. + private string? Compile( + string source, + string path, + ScanErrorHandler scanErrorHandler, + ParseErrorHandler parseErrorHandler, + NameResolutionErrorHandler nameResolutionErrorHandler, + ValidationErrorHandler typeValidationErrorHandler, + ValidationErrorHandler immutabilityValidationErrorHandler, + CompilerWarningHandler compilerWarningHandler) + { + string targetCppFile = Path.ChangeExtension(path, ".cc"); + +#if _WINDOWS + // clang is very unlikely to have been available on Windows anyway, but why not... + string targetExecutable = Path.ChangeExtension(targetCppFile, "exe"); +#else + string targetExecutable = Path.ChangeExtension(targetCppFile, null); +#endif + + if (!CompilationCacheDisabled && + File.GetCreationTime(targetCppFile) > File.GetCreationTime(path) && + File.GetCreationTime(targetExecutable) > File.GetCreationTime(path)) + { + // Both the .cc file and the executable are newer than the given Perlang script => no need to + // compile it. We presume the binary to be already up-to-date. + return targetExecutable; + } + + ScanAndParseResult result = PerlangParser.ScanAndParse( + source, + scanErrorHandler, + parseErrorHandler, + replMode: false + ); + + if (result == ScanAndParseResult.ScanErrorOccurred || + result == ScanAndParseResult.ParseErrorEncountered) + { + // These errors have already been propagated to the caller; we can simply return a this point. + return null; + } + + ImmutableList statements; + + if (result.HasStatements) + { + statements = result.Statements!.ToImmutableList(); + } + else if (result.HasExpr) + { + // In interpreted mode, we handle "single expressions" specially (so we can return their value to the + // caller). In compiled mode, single expressions typically don't make _sense_ but they are used by some unit + // test(s). We wrap them as statements to make the compiler be able to deal with them. + statements = ImmutableList.Create(new Stmt.ExpressionStmt(result.Expr!)); + } + else + { + throw new IllegalStateException("syntax was neither Expr nor list of Stmt"); + } + + // + // Resolving names phase + // + + bool hasNameResolutionErrors = false; + + var nameResolver = new NameResolver( + nativeClasses, + superGlobals, + BindingHandler, + AddGlobalClass, + nameResolutionError => + { + hasNameResolutionErrors = true; + nameResolutionErrorHandler(nameResolutionError); + } + ); + + nameResolver.Resolve(statements); + + if (hasNameResolutionErrors) + { + // Resolution errors has been reported back to the provided error handler. Nothing more remains + // to be done than aborting the evaluation. + return null; + } + + // + // Type validation + // + + bool typeValidationFailed = false; + + TypeValidator.Validate( + statements, + typeValidationError => + { + typeValidationFailed = true; + typeValidationErrorHandler(typeValidationError); + }, + BindingHandler.GetVariableOrFunctionBinding, + compilerWarning => + { + bool result = compilerWarningHandler(compilerWarning); + + if (result) + { + typeValidationFailed = true; + } + } + ); + + if (typeValidationFailed) + { + return null; + } + + // + // Immutability validation + // + + bool immutabilityValidationFailed = false; + + ImmutabilityValidator.Validate( + statements, + immutabilityValidationError => + { + immutabilityValidationFailed = true; + immutabilityValidationErrorHandler(immutabilityValidationError); + }, + BindingHandler.GetVariableOrFunctionBinding + ); + + if (immutabilityValidationFailed) + { + return null; + } + + // + // "Code analysis" validation + // + + bool codeAnalysisValidationFailed = false; + + CodeAnalysisValidator.Validate( + statements, + compilerWarning => + { + bool result = compilerWarningHandler(compilerWarning); + + if (result) + { + codeAnalysisValidationFailed = true; + } + } + ); + + if (codeAnalysisValidationFailed) + { + return null; + } + + try + { + // The AST traverser writes its output to the transpiledSource field. + Compile(statements); + + using (StreamWriter streamWriter = File.CreateText(targetCppFile)) + { + // Write standard file header, which includes everything our transpiled code might expect. + streamWriter.Write($@"// Automatically generated code by Perlang {CommonConstants.InformationalVersion} at {DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture)} +// Do not modify. Changes to this file might be overwritten the next time the Perlang compiler is executed. + +#include + +#include ""stdlib.hpp"" + +"); + + streamWriter.WriteLine("//"); + streamWriter.WriteLine("// Method definitions"); + streamWriter.WriteLine("//"); + + foreach (var (key, value) in methods) + { + streamWriter.WriteLine($"{value.ReturnType} {key}({value.ParametersString});"); + } + + streamWriter.WriteLine(); + streamWriter.WriteLine("//"); + streamWriter.WriteLine("// Method declarations"); + streamWriter.WriteLine("//"); + + foreach (var (key, value) in methods) + { + streamWriter.WriteLine($"{value.ReturnType} {key}({value.ParametersString}) {{"); + streamWriter.Write(value.MethodBody); + streamWriter.WriteLine('}'); + streamWriter.WriteLine(); + } + } + + string? perlangRoot = Environment.GetEnvironmentVariable("PERLANG_ROOT"); + + if (perlangRoot == null) + { + throw new PerlangCompilerException( + "The PERLANG_ROOT environment variable must be set for experimental compilation to succeed. " + + "It should point to a directory where the lib/stdlib directory contains a compiled version " + + "of the Perlang standard library."); + } + + var processStartInfo = new ProcessStartInfo + { + FileName = "clang++", + + ArgumentList = + { + // Required for nested namespaces + "--std=c++17", + + "-I", Path.Combine(perlangRoot, "lib/stdlib/include"), + "-o", targetExecutable, + + // Useful while debugging + //"-save-temps", + //"-v", + + // Regarding warnings: we enable everything and also the "strict" -Werror mode, but then also + // selectively disable warnings which we know will otherwise be triggered by our code generator. + "-Wall", + "-Werror", + + // C# allows cast from e.g. 2147483647 to float without warnings, but here's an interesting thing: + // clang emits a very nice warning for this ("implicit conversion from 'int' to 'float' changes + // value from 2147483647 to 2147483648"). We might want to consider doing something similar for + // Perlang in the end. + "-Wno-implicit-const-int-float-conversion", + + // 1073741824 * 2 causes a warning about this. As with other overflows, we could consider + // implementing warnings for them but we want them on the Perlang level instead of on clang level in + // that case. + "-Wno-integer-overflow", + + // Handled on the Perlang side + "-Wno-logical-op-parentheses", + + // We currently overflow without warnings, but we could consider implementing something like this + // warning in Perlang as well. Here's what the warning produces on `9223372036854775807 << 2`: + // error: signed shift result (0x1FFFFFFFFFFFFFFFC) requires 66 bits to represent, but 'long' only has 64 bits + "-Wno-shift-overflow", + + // 2147483647 >> 32 is valid in e.g. Java, but generates a warning with clang. The warning is useful + // but we silence it for now. + "-Wno-shift-count-overflow", + + // Doesn't make sense, but some of our unit tests produce such warnings + "-Wno-unused-value", + "-Wno-unused-variable", + + // Order here is critical: we must list the C++ source *before* the libstdlib.a reference, since the + // linker will otherwise be unable to resolve references from the C++ file to e.g. perlang::* + // methods. + targetCppFile, + + // TODO: Support Windows static libraries as well + Path.Combine(perlangRoot, "lib/stdlib/lib/libstdlib.a"), + + // Needed by the Perlang stdlib + "-lm" + }, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + using (Process? process = Process.Start(processStartInfo)) + { + if (process == null) + { + throw new PerlangCompilerException($"Launching process for {path} failed"); + } + + // To avoid deadlocks, always read the output stream first and then wait. + string stderrOutput = process.StandardError.ReadToEnd(); + + process.WaitForExit(); + + if (process.ExitCode != 0) + { + throw new PerlangCompilerException($"Internal compiler error: compiling transpiled source {targetCppFile} failed. Detailed error will follow:{Environment.NewLine}" + + $"{Environment.NewLine}" + + $"{stderrOutput}"); + } + + return targetExecutable; + } + } + catch (RuntimeError e) + { + runtimeErrorHandler(e); + return null; + } + } + + /// + /// Entry-point for compiling one or more statements. + /// + /// An enumerator for a collection of statements. + private void Compile(IEnumerable statements) + { + foreach (Stmt statement in statements) + { + Compile(statement); + } + } + + private void Compile(Stmt stmt) + { + stmt.Accept(this); + } + + private void AddGlobalClass(string name, PerlangClass perlangClass) + { + globalClasses[name] = perlangClass; + } + + public object? VisitEmptyExpr(Expr.Empty expr) + { + throw new NotImplementedException(); + } + + public object? VisitAssignExpr(Expr.Assign expr) + { + currentMethod.Append($"{expr.Identifier.Name.Lexeme} = {expr.Value.Accept(this)}"); + return VoidObject.Void; + } + + public object? VisitBinaryExpr(Expr.Binary expr) + { + switch (expr.Operator.Type) + { + // + // Comparison operators + // + // IComparable would be useful to reduce code duplication here, but it has one major problem: it only + // supports same-type comparisons (int+int, long+long etc). We do not want to limit our code like that. + // + + // Note: do **NOT** add new operators here without adding corresponding tests. (in the future, try to + // enforce this via e.g. ArchUnit.NET or a unit test) + + case GREATER: + // TODO: We might need something like CheckNumberOperands() in PerlangInterpreter here, but we can obviously not do any value-based checking since we are a compiler and no + currentMethod.Append($"{expr.Left.Accept(this)} > {expr.Right.Accept(this)}"); + break; + + case GREATER_EQUAL: + currentMethod.Append($"{expr.Left.Accept(this)} >= {expr.Right.Accept(this)}"); + break; + + case LESS: + currentMethod.Append($"{expr.Left.Accept(this)} < {expr.Right.Accept(this)}"); + break; + + case LESS_EQUAL: + currentMethod.Append($"{expr.Left.Accept(this)} < {expr.Right.Accept(this)}"); + break; + + case BANG_EQUAL: + currentMethod.Append($"{expr.Left.Accept(this)} != {expr.Right.Accept(this)}"); + break; + + case EQUAL_EQUAL: + currentMethod.Append($"{expr.Left.Accept(this)} == {expr.Right.Accept(this)}"); + break; + + // + // Arithmetic operators + // + // Note: many of these checks could be done at an earlier stage, to ensure e.g. valid numeric types for the + // operators which require it. We don't currently have any such checks in place, though. + // + + case MINUS: + if (expr.Left.TypeReference.IsValidNumberType && expr.Right.TypeReference.IsValidNumberType) + { + currentMethod.Append($"{expr.Left.Accept(this)} - {expr.Right.Accept(this)}"); + } + else + { + string message = CompilerMessages.UnsupportedOperatorTypeInBinaryExpression(expr.Operator.Type); + throw new RuntimeError(expr.Operator, message); + } + + break; + + case MINUS_EQUAL: + if (expr.Left.TypeReference.IsValidNumberType && expr.Right.TypeReference.IsValidNumberType) + { + currentMethod.Append($"{expr.Left.Accept(this)} -= {expr.Right.Accept(this)}"); + } + else + { + string message = CompilerMessages.UnsupportedOperatorTypeInBinaryExpression(expr.Operator.Type); + throw new RuntimeError(expr.Operator, message); + } + + break; + + case PLUS: + if (expr.Left.TypeReference.IsValidNumberType && expr.Right.TypeReference.IsValidNumberType) + { + currentMethod.Append($"{expr.Left.Accept(this)} + {expr.Right.Accept(this)}"); + } + else if (expr.Left.TypeReference.IsStringType() && expr.Right.TypeReference.IsStringType()) + { + throw new NotImplementedInCompiledModeException($"Concatenation between {expr.Left.TypeReference.ClrType.ToTypeKeyword()} and {expr.Right.TypeReference.ClrType.ToTypeKeyword()} is not yet implemented"); + } + else if (expr.Left.TypeReference.IsValidNumberType && expr.Right.TypeReference.IsStringType()) + { + throw new NotImplementedInCompiledModeException($"Concatenation between {expr.Left.TypeReference.ClrType.ToTypeKeyword()} and {expr.Right.TypeReference.ClrType.ToTypeKeyword()} is not yet implemented"); + } + else if (expr.Left.TypeReference.IsStringType() && expr.Right.TypeReference.IsValidNumberType) + { + throw new NotImplementedInCompiledModeException($"Concatenation between {expr.Left.TypeReference.ClrType.ToTypeKeyword()} and {expr.Right.TypeReference.ClrType.ToTypeKeyword()} is not yet implemented"); + } + else + { + // TODO: Strings/other types need different handling, and are more complex since they will + // TODO: inherently require memory allocation. We don't have a defined model for when that + // TODO: memory would be deallocated. + string message = CompilerMessages.UnsupportedOperandsInBinaryExpression(expr.Operator.Type, expr.Left.TypeReference, expr.Right.TypeReference); + throw new RuntimeError(expr.Operator, message); + } + + break; + + case PLUS_EQUAL: + if (expr.Left.TypeReference.IsValidNumberType && expr.Right.TypeReference.IsValidNumberType) + { + currentMethod.Append($"{expr.Left.Accept(this)} += {expr.Right.Accept(this)}"); + } + else + { + string message = CompilerMessages.UnsupportedOperatorTypeInBinaryExpression(expr.Operator.Type); + throw new RuntimeError(expr.Operator, message); + } + + break; + + case SLASH: + if (expr.Left.TypeReference.IsValidNumberType && expr.Right.TypeReference.IsValidNumberType) + { + currentMethod.Append($"{expr.Left.Accept(this)} / {expr.Right.Accept(this)}"); + } + else + { + string message = CompilerMessages.UnsupportedOperatorTypeInBinaryExpression(expr.Operator.Type); + throw new RuntimeError(expr.Operator, message); + } + + break; + + case STAR: + if (expr.Left.TypeReference.IsValidNumberType && expr.Right.TypeReference.IsValidNumberType) + { + currentMethod.Append($"{expr.Left.Accept(this)} * {expr.Right.Accept(this)}"); + } + else + { + string message = CompilerMessages.UnsupportedOperatorTypeInBinaryExpression(expr.Operator.Type); + throw new RuntimeError(expr.Operator, message); + } + + break; + + case STAR_STAR: + throw new NotImplementedInCompiledModeException("** operator is not yet supported in compiled mode"); + + case PERCENT: + if (expr.Left.TypeReference.IsValidNumberType && expr.Right.TypeReference.IsValidNumberType) + { + currentMethod.Append($"{expr.Left.Accept(this)} % {expr.Right.Accept(this)}"); + } + else + { + string message = CompilerMessages.UnsupportedOperatorTypeInBinaryExpression(expr.Operator.Type); + throw new RuntimeError(expr.Operator, message); + } + + break; + + case LESS_LESS: + if (expr.Left.TypeReference.IsValidNumberType && expr.Right.TypeReference.IsValidNumberType) + { + if (expr.TypeReference.ClrType == typeof(BigInteger)) + { + throw new NotImplementedInCompiledModeException("<< operator with BigInteger results is currently not supported in compiled mode"); + } + else + { + // Additional parentheses emitted since C and C++ have the "wrong" (from a Perlang and K&R POV) + // precedence for the shift left operator, which clang is kind enough to warn us about. + currentMethod.Append($"({expr.Left.Accept(this)} << {expr.Right.Accept(this)})"); + } + } + else + { + string message = CompilerMessages.UnsupportedOperatorTypeInBinaryExpression(expr.Operator.Type); + throw new RuntimeError(expr.Operator, message); + } + + break; + + case GREATER_GREATER: + if (expr.Left.TypeReference.IsValidNumberType && expr.Right.TypeReference.IsValidNumberType) + { + if (expr.TypeReference.ClrType == typeof(BigInteger)) + { + throw new NotImplementedInCompiledModeException("<< operator with BigInteger results is currently not supported in compiled mode"); + } + else + { + // Additional parentheses emitted since C and C++ have the "wrong" (from a Perlang and K&R POV) + // precedence for the shift left operator, which clang is kind enough to warn us about. + currentMethod.Append($"({expr.Left.Accept(this)} >> {expr.Right.Accept(this)})"); + } + } + else + { + string message = CompilerMessages.UnsupportedOperatorTypeInBinaryExpression(expr.Operator.Type); + throw new RuntimeError(expr.Operator, message); + } + + break; + + default: + { + string message = CompilerMessages.UnsupportedOperatorTypeInBinaryExpression(expr.Operator.Type); + throw new RuntimeError(expr.Operator, message); + } + } + + return VoidObject.Void; + } + + public object? VisitCallExpr(Expr.Call expr) + { + // TODO: In interpreted mode, information about the callee is not really known at this point; we get information + // TODO: about it by calling Evaluate(expr.Callee). In compiled mode, it would probably make sense to do method + // TODO: binding at an earlier stage and e.g. validate the number and type of arguments with the method + // TODO: definition. Right now, we'll leave this to the C++ compiler to deal with... + + currentMethod.Append(expr.Callee.Accept(this)); + currentMethod.Append('('); + + for (int index = 0; index < expr.Arguments.Count; index++) + { + Expr argument = expr.Arguments[index]; + argument.Accept(this); + + if (index < expr.Arguments.Count - 1) + { + currentMethod.Append(", "); + } + } + + currentMethod.Append(')'); + + return VoidObject.Void; + } + + public object? VisitIndexExpr(Expr.Index expr) + { + // TODO: Consider doing range checking at this point. In general, try to figure out a balanced approach in the + // TODO: "safe" vs "fast" dichotomy. C and C++ are fast (no array bounds checking) but can blow up in your face + // TODO: if you're not careful. Java and .NET are safe (all array indexing is bounds-checked) but this + // TODO: inherently *will* have a performance impact. Sometimes this is negligible but sometimes not. + // TODO: + // TODO: One way to deal with this: make `foo[bar]` be safe and add an "unsafe" indexing operator `foo[[bar]]` + // TODO: which can be used when the user believes he knows best. Investigate how Rust is doing this. + expr.Indexee.Accept(this); + currentMethod.Append('['); + expr.Argument.Accept(this); + currentMethod.Append(']'); + + return VoidObject.Void; + } + + public object? VisitGroupingExpr(Expr.Grouping expr) + { + currentMethod.Append('('); + expr.Expression.Accept(this); + currentMethod.Append(')'); + + return VoidObject.Void; + } + + public object VisitLiteralExpr(Expr.Literal expr) + { + if (expr.Value is AsciiString or Utf8String) + { + // Wrap in double quotes. + currentMethod.Append('"'); + currentMethod.Append(expr); + currentMethod.Append('"'); + + // TODO: Support our other string types as well + } + else if (expr.Value == null) + { + // C++11 defines nullptr as a keyword + currentMethod.Append("nullptr"); + } + else if (expr.Value is IntegerLiteral uintLiteral) + { + // TODO: should be UL when targeting 32-bit platforms and U for 64-bit. + currentMethod.Append(uintLiteral.Value); + currentMethod.Append('U'); // unsigned + } + else if (expr.Value is IntegerLiteral ulongLiteral) + { + // FIXME: should be ULL for 32-bit platforms + currentMethod.Append(ulongLiteral.Value); + currentMethod.Append("UL"); // unsigned long + } + else if (expr.Value is INumericLiteral numericLiteral) + { + // This will work for all other numeric types apart from BigInteger, for which we need to do some special + // magic to make them work in compiled mode. + currentMethod.Append(numericLiteral.Value); + } + else if (expr.Value is bool boolLiteral) + { + // true.ToString() == "True", which is why we need this manual logic. + currentMethod.Append(boolLiteral ? "true" : "false"); + } + else + { + throw new PerlangCompilerException($"Internal compiler error: unsupported type {expr.Value.GetType().ToTypeKeyword()} encountered"); + } + + return VoidObject.Void; + } + + public object? VisitLogicalExpr(Expr.Logical expr) + { + switch (expr.Operator.Type) + { + case PIPE_PIPE: + expr.Left.Accept(this); + currentMethod.Append("||"); + expr.Right.Accept(this); + break; + + case AMPERSAND_AMPERSAND: + expr.Left.Accept(this); + currentMethod.Append("&&"); + expr.Right.Accept(this); + break; + + default: + string message = CompilerMessages.UnsupportedOperatorTypeInLogicalExpression(expr.Operator.Type); + throw new RuntimeError(expr.Operator, message); + } + + return VoidObject.Void; + } + + public object? VisitUnaryPrefixExpr(Expr.UnaryPrefix expr) + { + switch (expr.Operator.Type) + { + case BANG: + currentMethod.Append('!'); + expr.Right.Accept(this); + break; + + case MINUS: + currentMethod.Append('-'); + expr.Right.Accept(this); + break; + + default: + string message = CompilerMessages.UnsupportedOperatorTypeInUnaryPrefixExpression(expr.Operator.Type); + throw new RuntimeError(expr.Operator, message); + } + + return VoidObject.Void; + } + + public object? VisitUnaryPostfixExpr(Expr.UnaryPostfix expr) + { + switch (expr.Operator.Type) + { + case PLUS_PLUS: + expr.Left.Accept(this); + currentMethod.Append("++"); + break; + + case MINUS_MINUS: + expr.Left.Accept(this); + currentMethod.Append("--"); + break; + + default: + string message = CompilerMessages.UnsupportedOperatorTypeInUnaryPostfixExpression(expr.Operator.Type); + throw new RuntimeError(expr.Operator, message); + } + + return VoidObject.Void; + } + + public object? VisitIdentifierExpr(Expr.Identifier expr) + { + currentMethod.Append(expr.Name.Lexeme); + return VoidObject.Void; + } + + public object? VisitGetExpr(Expr.Get expr) + { + if (expr.Object is Expr.Identifier identifier) + { + if (globalClasses.ContainsKey(identifier.Name.Lexeme)) + { + // These classes are put in the `perlang::stdlib` namespace in the C++ world + currentMethod.Append($"perlang::stdlib::{identifier.Name.Lexeme}"); + + // Static methods are called with `class::method(params)` syntax in C++ + currentMethod.Append("::"); + currentMethod.Append(expr.Name.Lexeme); + } + else + { + // TODO: We need to differentiate here between different kind of identifiers. + throw new NotImplementedInCompiledModeException($"Calling methods on {identifier} is not yet implemented in compiled mode"); + } + } + else if (expr.Object is Expr.Grouping grouping) + { + // TODO: Something like this would do eventually... but until we support user-defined classes, it's kind of + // TODO: moot anyway. It's pretty much impossible to perform a method call like e.g. 42.get_type() in C or + // TODO: C++. We would at the very least have to special-case all primitives, to emulate something similar + // TODO: in the Perlang world. + grouping.Accept(this); + + currentMethod.Append('.'); + currentMethod.Append(expr.Name.Lexeme); + } + else + { + throw new PerlangCompilerException($"Internal compiler error: unhandled type of get expression: {expr.Object}"); + } + + return VoidObject.Void; + } + + public VoidObject VisitBlockStmt(Stmt.Block block) + { + currentMethod.Append(Indent(indentationLevel)); + currentMethod.AppendLine("{"); + indentationLevel++; + + foreach (Stmt stmt in block.Statements) + { + stmt.Accept(this); + } + + indentationLevel--; + currentMethod.Append(Indent(indentationLevel)); + currentMethod.AppendLine("}"); + + return VoidObject.Void; + } + + public VoidObject VisitClassStmt(Stmt.Class stmt) + { + throw new NotImplementedException(); + } + + public VoidObject VisitExpressionStmt(Stmt.ExpressionStmt stmt) + { + // We only emit the "wrapping" (whitespace before + semicolon and newline after expression) in this case; the + // rest comes from visiting the expression itself + currentMethod.Append(Indent(indentationLevel)); + stmt.Expression.Accept(this); + currentMethod.AppendLine(";"); + + return VoidObject.Void; + } + + public VoidObject VisitFunctionStmt(Stmt.Function functionStmt) + { + StringBuilder previousMethod = currentMethod; + + currentMethod = new StringBuilder(); + + methods[functionStmt.Name.Lexeme] = new Method( + functionStmt.Name.Lexeme, + functionStmt.Parameters, + functionStmt.ReturnTypeReference.CppType, + currentMethod + ); + + // Loop over the statements in the method body and visit them, recursively + foreach (Stmt stmt in functionStmt.Body) + { + stmt.Accept(this); + } + + currentMethod = previousMethod; + + return VoidObject.Void; + } + + public VoidObject VisitIfStmt(Stmt.If stmt) + { + currentMethod.Append(Indent(indentationLevel)); + currentMethod.Append($"if ({stmt.Condition.Accept(this)}) "); + + if (!(stmt.ThenBranch is Stmt.Block)) + { + currentMethod.AppendLine(); + } + + indentationLevel++; + stmt.ThenBranch.Accept(this); + indentationLevel--; + + if (stmt.ElseBranch != null) + { + currentMethod.Append(Indent(indentationLevel)); + currentMethod.Append("else "); + + if (!(stmt.ElseBranch is Stmt.Block)) + { + currentMethod.AppendLine(); + } + + indentationLevel++; + stmt.ElseBranch.Accept(this); + indentationLevel--; + } + + return VoidObject.Void; + } + + public VoidObject VisitPrintStmt(Stmt.Print stmt) + { + currentMethod.AppendLine($"{Indent(indentationLevel)}perlang::print({stmt.Expression.Accept(this)});"); + + return VoidObject.Void; + } + + public VoidObject VisitReturnStmt(Stmt.Return stmt) + { + if (stmt.Value == null) + { + // void return + currentMethod.AppendLine($"{Indent(indentationLevel)}return;"); + } + else + { + currentMethod.Append($"{Indent(indentationLevel)}return "); + stmt.Value.Accept(this); + currentMethod.AppendLine(";"); + } + + return VoidObject.Void; + } + + public VoidObject VisitVarStmt(Stmt.Var stmt) + { + currentMethod.Append($"{Indent(indentationLevel)}{stmt.TypeReference.CppType} {stmt.Name.Lexeme}"); + + if (stmt.HasInitializer) + { + currentMethod.AppendLine($" = {stmt.Initializer.Accept(this)};"); + } + else + { + currentMethod.AppendLine(";"); + } + + return VoidObject.Void; + } + + public VoidObject VisitWhileStmt(Stmt.While whileStmt) + { + currentMethod.Append(Indent(indentationLevel)); + currentMethod.Append($"while ({whileStmt.Condition.Accept(this)}) "); + whileStmt.Body.Accept(this); + currentMethod.AppendLine(";"); + + return VoidObject.Void; + } + + private static string Indent(int level) => String.Empty.PadLeft(level * 4); + + private record Method(string Name, IImmutableList Parameters, string ReturnType, StringBuilder MethodBody) + { + /// + /// Gets the method parameters as a comma-separated string, in C++ format. Example: `int foo, int bar`. + /// + public string ParametersString { get; } = String.Join(", ", Parameters.Select(p => $"{p.TypeReference.CppType} {p.Name.Lexeme}")); + } +} diff --git a/src/Perlang.Interpreter/PerlangInterpreter.cs b/src/Perlang.Interpreter/PerlangInterpreter.cs index 4489949f..c1a788ab 100644 --- a/src/Perlang.Interpreter/PerlangInterpreter.cs +++ b/src/Perlang.Interpreter/PerlangInterpreter.cs @@ -181,10 +181,11 @@ private void RegisterGlobalClasses() return null; } - ScanAndParseResult result = ScanAndParse( + ScanAndParseResult result = PerlangParser.ScanAndParse( source, scanErrorHandler, - parseErrorHandler + parseErrorHandler, + replMode ); if (result == ScanAndParseResult.ScanErrorOccurred || @@ -323,7 +324,7 @@ private void RegisterGlobalClasses() // validation steps on the complete program now (all the statements executed up to now + the expression // we just received). var previousAndNewStatements = previousStatements - .Concat(ImmutableList.Create(new Stmt.ExpressionStmt(result.Expr))) + .Concat(ImmutableList.Create(new Stmt.ExpressionStmt(result.Expr!))) .ToImmutableList(); // @@ -441,81 +442,6 @@ private void RegisterGlobalClasses() } } - /// - /// Scans and parses the given program, to prepare for evaluation. - /// - /// This method is useful for inspecting the AST or perform other validation of the internal state after - /// parsing a given program. - /// - /// The source code to a Perlang program (typically a single line of Perlang code). - /// A handler for scanner errors. - /// A handler for parse errors. - /// A instance. - internal ScanAndParseResult ScanAndParse( - string source, - ScanErrorHandler scanErrorHandler, - ParseErrorHandler parseErrorHandler) - { - // - // Scanning phase - // - - bool hasScanErrors = false; - var scanner = new Scanner(source, scanError => - { - hasScanErrors = true; - scanErrorHandler(scanError); - }); - - var tokens = scanner.ScanTokens(); - - if (hasScanErrors) - { - // Something went wrong as early as the "scan" stage. Abort the rest of the processing. - return ScanAndParseResult.ScanErrorOccurred; - } - - // - // Parsing phase - // - - bool hasParseErrors = false; - var parser = new PerlangParser( - tokens, - parseError => - { - hasParseErrors = true; - parseErrorHandler(parseError); - }, - allowSemicolonElision: replMode - ); - - object syntax = parser.ParseExpressionOrStatements(); - - if (hasParseErrors) - { - // One or more parse errors were encountered. They have been reported upstream, so we just abort - // the evaluation at this stage. - return ScanAndParseResult.ParseErrorEncountered; - } - - // TODO: Should we return here (and change PrepareForEvalResult to something like ScanAndParseResult) or - // should we continue with moving more of the code in eval into this method? Given that we want to inspect - // the result from Resolver, maybe doing more work here would be completely fine... - if (syntax is Expr expr) - { - return ScanAndParseResult.OfExpr(expr); - } - else if (syntax is List stmts) - { - return ScanAndParseResult.OfStmts(stmts); - } - else - { - throw new IllegalStateException($"syntax expected to be Expr or List, not {syntax}"); - } - } - /// /// Parses the provided source code and returns a string representation of the parsed AST. /// @@ -2758,44 +2684,5 @@ public VoidObject VisitWhileStmt(Stmt.While stmt) return value; } - - /// - /// Contains the result of the method. - /// - internal class ScanAndParseResult - { - public static ScanAndParseResult ScanErrorOccurred { get; } = new(); - public static ScanAndParseResult ParseErrorEncountered { get; } = new(); - - public Expr? Expr { get; } - public List? Statements { get; } - - public bool HasExpr => Expr != null; - public bool HasStatements => Statements != null; - - private ScanAndParseResult() - { - } - - private ScanAndParseResult(Expr expr) - { - Expr = expr; - } - - private ScanAndParseResult(List statements) - { - Statements = statements; - } - - public static ScanAndParseResult OfExpr(Expr expr) - { - return new ScanAndParseResult(expr); - } - - public static ScanAndParseResult OfStmts(List stmts) - { - return new ScanAndParseResult(stmts); - } - } } } diff --git a/src/Perlang.Interpreter/Typing/TypeValidator.cs b/src/Perlang.Interpreter/Typing/TypeValidator.cs index d4d32891..48722359 100644 --- a/src/Perlang.Interpreter/Typing/TypeValidator.cs +++ b/src/Perlang.Interpreter/Typing/TypeValidator.cs @@ -32,7 +32,7 @@ public static void Validate( bool typeResolvingFailed = false; // - // Phase 1: Resolve explicit and explicit type references to their corresponding CLR types. + // Phase 1: Resolve explicit and implicit type references to their corresponding CLR types. // var typeResolver = new TypeResolver( getVariableOrFunctionCallback, diff --git a/src/Perlang.Parser/PerlangParser.cs b/src/Perlang.Parser/PerlangParser.cs index 775f82c7..1f71efb5 100644 --- a/src/Perlang.Parser/PerlangParser.cs +++ b/src/Perlang.Parser/PerlangParser.cs @@ -4,12 +4,14 @@ // try and send a PR. #pragma warning disable SA1503 +#pragma warning disable S1117 using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using Perlang.Exceptions; using static Perlang.Internal.Utils; using static Perlang.TokenType; @@ -46,6 +48,85 @@ public InternalParseError(ParseErrorType? parseErrorType) private int current; + /// + /// Scans and parses the given program, to prepare for execution or inspection. + /// + /// This method is useful for inspecting the AST or perform other validation of the internal state after + /// parsing a given program. It is also used for interpreting the expressions/statements, or compiling it to + /// executable form. + /// + /// The source code to a Perlang program (typically a single line of Perlang code). + /// A handler for scanner errors. + /// A handler for parse errors. + /// `true` if the program is being executed in REPL mode; otherwise, `false`. REPL mode + /// implies a more relaxed more where e.g. semicolons are automatically added after each line. + /// A instance. + public static ScanAndParseResult ScanAndParse( + string source, + ScanErrorHandler scanErrorHandler, + ParseErrorHandler parseErrorHandler, + bool replMode = false) + { + // + // Scanning phase + // + + bool hasScanErrors = false; + var scanner = new Scanner(source, scanError => + { + hasScanErrors = true; + scanErrorHandler(scanError); + }); + + var tokens = scanner.ScanTokens(); + + if (hasScanErrors) + { + // Something went wrong as early as the "scan" stage. Abort the rest of the processing. + return ScanAndParseResult.ScanErrorOccurred; + } + + // + // Parsing phase + // + + bool hasParseErrors = false; + var parser = new PerlangParser( + tokens, + parseError => + { + hasParseErrors = true; + parseErrorHandler(parseError); + }, + allowSemicolonElision: replMode + ); + + object syntax = parser.ParseExpressionOrStatements(); + + if (hasParseErrors) + { + // One or more parse errors were encountered. They have been reported upstream, so we just abort + // the evaluation at this stage. + return ScanAndParseResult.ParseErrorEncountered; + } + + // TODO: Should we return here (and change PrepareForEvalResult to something like ScanAndParseResult) or + // should we continue with moving more of the code in eval into this method? Given that we want to inspect + // the result from Resolver, maybe doing more work here would be completely fine... + if (syntax is Expr expr) + { + return ScanAndParseResult.OfExpr(expr); + } + else if (syntax is List stmts) + { + return ScanAndParseResult.OfStmts(stmts); + } + else + { + throw new IllegalStateException($"syntax expected to be Expr or List, not {syntax}"); + } + } + public PerlangParser(List tokens, ParseErrorHandler parseErrorHandler, bool allowSemicolonElision) { this.parseErrorHandler = parseErrorHandler; diff --git a/src/Perlang.Parser/Properties/AssemblyInfo.cs b/src/Perlang.Parser/Properties/AssemblyInfo.cs index fdc9a23d..2f67c687 100644 --- a/src/Perlang.Parser/Properties/AssemblyInfo.cs +++ b/src/Perlang.Parser/Properties/AssemblyInfo.cs @@ -4,4 +4,11 @@ [assembly: AssemblyVersion(CommonConstants.Version)] [assembly: AssemblyInformationalVersion(CommonConstants.InformationalVersion)] + +// This is a bit of a code smell, but: we want to expose these internal types to the Perlang interpreter and compiler, +// but not necessarily make them part of the "public Perlang API" which could potentially be used by a JetBrains Rider +// extension, VS Code language server or similar. Hopefully, this will all become moot since we become self-hosting fast +// enough but time will tell. :-) +[assembly: InternalsVisibleTo("Perlang.Interpreter")] + [assembly: InternalsVisibleTo("Perlang.Tests")] diff --git a/src/Perlang.Parser/ScanAndParseResult.cs b/src/Perlang.Parser/ScanAndParseResult.cs new file mode 100644 index 00000000..d953c973 --- /dev/null +++ b/src/Perlang.Parser/ScanAndParseResult.cs @@ -0,0 +1,43 @@ +#nullable enable +using System.Collections.Generic; + +namespace Perlang.Parser; + +/// +/// Contains the result of the method. +/// +public class ScanAndParseResult +{ + public static ScanAndParseResult ScanErrorOccurred { get; } = new(); + public static ScanAndParseResult ParseErrorEncountered { get; } = new(); + + public Expr? Expr { get; } + public List? Statements { get; } + + public bool HasExpr => Expr != null; + public bool HasStatements => Statements != null; + + private ScanAndParseResult() + { + } + + private ScanAndParseResult(Expr expr) + { + Expr = expr; + } + + private ScanAndParseResult(List statements) + { + Statements = statements; + } + + public static ScanAndParseResult OfExpr(Expr expr) + { + return new ScanAndParseResult(expr); + } + + public static ScanAndParseResult OfStmts(List stmts) + { + return new ScanAndParseResult(stmts); + } +} diff --git a/src/Perlang.Stdlib/Internal/Utils.cs b/src/Perlang.Stdlib/Internal/Utils.cs index 9825a922..4407098e 100644 --- a/src/Perlang.Stdlib/Internal/Utils.cs +++ b/src/Perlang.Stdlib/Internal/Utils.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.Numerics; using Perlang.Internal.Extensions; using Perlang.Lang; using String = Perlang.Lang.String; diff --git a/src/Perlang.Tests.Integration/Classes/ClassesTests.cs b/src/Perlang.Tests.Integration/Classes/ClassesTests.cs index 6aae8e91..e9f48112 100644 --- a/src/Perlang.Tests.Integration/Classes/ClassesTests.cs +++ b/src/Perlang.Tests.Integration/Classes/ClassesTests.cs @@ -40,9 +40,11 @@ class Foo {} Assert.Matches("Class Foo already defined; cannot redefine", exception.Message); } - [Fact] + [SkippableFact] public void native_class_can_be_accessed_by_name() { + Skip.If(PerlangMode.ExperimentalCompilation, "Not supported in compiled mode"); + // For now, all native classes are registered in the global namespace. We could consider changing this, // since a global namespace is a precious thing that should be treated as such, preventing unnecessary // pollution. As long as we use this mechanism for mostly providing system-utilities that are widely useful @@ -132,9 +134,13 @@ class Foo {} Assert.Equal("#", output); } - [Fact] + [SkippableFact] public void can_get_reference_to_static_method_native_class() { + // We need to figure out a way to handle method references in compiled mode, and once we've done that, how + // to `print()` it in a reasonable manner. :-) + Skip.If(PerlangMode.ExperimentalCompilation, "Not supported in compiled mode"); + string source = @" print Base64.to_string; "; diff --git a/src/Perlang.Tests.Integration/Comments/CommentsTests.cs b/src/Perlang.Tests.Integration/Comments/CommentsTests.cs index b5cc7e25..c7f1bfb4 100644 --- a/src/Perlang.Tests.Integration/Comments/CommentsTests.cs +++ b/src/Perlang.Tests.Integration/Comments/CommentsTests.cs @@ -1,3 +1,4 @@ +using FluentAssertions; using Xunit; using static Perlang.Tests.Integration.EvalHelper; @@ -27,9 +28,10 @@ public void only_line_comment() { string source = "// comment"; - var output = Eval(source); + var output = EvalReturningOutputString(source); - Assert.Null(output); + output.Should() + .BeEmpty(); } [Fact] @@ -37,9 +39,10 @@ public void only_line_comment_and_line() { string source = "// comment\n"; - var output = Eval(source); + var output = EvalReturningOutputString(source); - Assert.Null(output); + output.Should() + .BeEmpty(); } [Fact] diff --git a/src/Perlang.Tests.Integration/EvalHelper.cs b/src/Perlang.Tests.Integration/EvalHelper.cs index 1375b7e8..6b10c1d7 100644 --- a/src/Perlang.Tests.Integration/EvalHelper.cs +++ b/src/Perlang.Tests.Integration/EvalHelper.cs @@ -1,8 +1,14 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using Perlang.Compiler; using Perlang.Interpreter; +using Perlang.Interpreter.Compiler; using Perlang.Interpreter.NameResolution; using Perlang.Parser; +using Xunit; namespace Perlang.Tests.Integration { @@ -25,17 +31,24 @@ internal static class EvalHelper /// statements. internal static object Eval(string source) { - var interpreter = new PerlangInterpreter(AssertFailRuntimeErrorHandler, _ => { }); - - return interpreter.Eval( - source, - AssertFailScanErrorHandler, - AssertFailParseErrorHandler, - AssertFailNameResolutionErrorHandler, - AssertFailValidationErrorHandler, - AssertFailValidationErrorHandler, - AssertFailCompilerWarningHandler - ); + if (PerlangMode.ExperimentalCompilation) + { + throw new SkipException("Evaluating and returning a result is not supported in experimental compilation mode"); + } + else + { + var interpreter = new PerlangInterpreter(AssertFailRuntimeErrorHandler, _ => { }); + + return interpreter.Eval( + source, + AssertFailScanErrorHandler, + AssertFailParseErrorHandler, + AssertFailNameResolutionErrorHandler, + AssertFailValidationErrorHandler, + AssertFailValidationErrorHandler, + AssertFailCompilerWarningHandler + ); + } } /// @@ -230,20 +243,71 @@ internal static EvalResult EvalWithValidationErrorCatch(string /// will be set to `null`. internal static EvalResult EvalWithResult(string source, params string[] arguments) { - var result = new EvalResult(); - var interpreter = new PerlangInterpreter(AssertFailRuntimeErrorHandler, result.OutputHandler, null, arguments); - - result.Value = interpreter.Eval( - source, - AssertFailScanErrorHandler, - AssertFailParseErrorHandler, - AssertFailNameResolutionErrorHandler, - AssertFailValidationErrorHandler, - AssertFailValidationErrorHandler, - result.WarningHandler - ); + if (PerlangMode.ExperimentalCompilation) + { + var result = new EvalResult(); + var compiler = new PerlangCompiler(AssertFailRuntimeErrorHandler, result.OutputHandler, null, arguments); + + try + { + result.ExecutablePath = compiler.CompileAndRun( + source, + CreateTemporaryPath(source), + AssertFailScanErrorHandler, + AssertFailParseErrorHandler, + AssertFailNameResolutionErrorHandler, + AssertFailValidationErrorHandler, + AssertFailValidationErrorHandler, + result.WarningHandler + ); + } + catch (NotImplementedInCompiledModeException e) + { + // This exception is thrown to make it possible for integration tests to skip tests for code which + // is known to not yet work. + throw new SkipException(e.Message); + } + + // Return something else than `null` to make it reasonable for callers to distinguish that compiled mode + // (with no native "result") is being used, if needed. + result.Value = VoidObject.Void; + + return result; + } + else + { + var result = new EvalResult(); + var interpreter = new PerlangInterpreter(AssertFailRuntimeErrorHandler, result.OutputHandler, null, arguments); + + result.Value = interpreter.Eval( + source, + AssertFailScanErrorHandler, + AssertFailParseErrorHandler, + AssertFailNameResolutionErrorHandler, + AssertFailValidationErrorHandler, + AssertFailValidationErrorHandler, + result.WarningHandler + ); + + return result; + } + } - return result; + private static string CreateTemporaryPath(string source) + { + // Note: this is obviously very Linux-specific. We need to figure out a good way to do this on macOS and + // Windows as well. + string xdgRuntimeDir = Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR"); + string perlangTempDir = Path.Join(xdgRuntimeDir, "perlang", "tmp", "unit_tests"); + + Directory.CreateDirectory(perlangTempDir); + + // The idea is to use a hashed version of the source as the file name, which means that a given Perlang + // program will reliably always produce the same hash => can use the caching mechanism already in place to + // avoid meaningless recompilation. (Might want to add a `make cache_clear` target or similar.) + byte[] sourceAsBytes = Encoding.UTF8.GetBytes(source); + byte[] sourceHash = SHA256.HashData(sourceAsBytes); + return Path.Join(perlangTempDir, Convert.ToHexString(sourceHash) + ".per"); } /// diff --git a/src/Perlang.Tests.Integration/EvalResult.cs b/src/Perlang.Tests.Integration/EvalResult.cs index c683967a..241103a5 100644 --- a/src/Perlang.Tests.Integration/EvalResult.cs +++ b/src/Perlang.Tests.Integration/EvalResult.cs @@ -41,6 +41,12 @@ internal class EvalResult /// public IReadOnlyList CompilerWarnings => compilerWarnings.AsReadOnly(); + /// + /// Gets or sets the path to the executable which was created as part of this evaluation. Will be `null` when + /// running in interpreted mode. + /// + public string? ExecutablePath { get; set; } + private readonly List compilerWarnings = new(); public void ErrorHandler(T error) diff --git a/src/Perlang.Tests.Integration/For/Syntax.cs b/src/Perlang.Tests.Integration/For/Syntax.cs index a5e3f750..656210a7 100644 --- a/src/Perlang.Tests.Integration/For/Syntax.cs +++ b/src/Perlang.Tests.Integration/For/Syntax.cs @@ -1,3 +1,4 @@ +using FluentAssertions; using Xunit; using static Perlang.Tests.Integration.EvalHelper; @@ -128,7 +129,10 @@ public void statement_bodies() var output = EvalReturningOutput(source); - Assert.Equal(new string[] { }, output); + // TODO: Nothing is expected to have been printed by the above, but perhaps we could make this test better + // TODO: and print in the branches? + output.Should() + .BeEmpty(); } } } diff --git a/src/Perlang.Tests.Integration/Function/Return.cs b/src/Perlang.Tests.Integration/Function/Return.cs index 820b91f3..ed3baedf 100644 --- a/src/Perlang.Tests.Integration/Function/Return.cs +++ b/src/Perlang.Tests.Integration/Function/Return.cs @@ -12,6 +12,9 @@ public void function_can_return_values_in_if_statement() fun f(i: int): int { if (i < 10) return i; if (i >= 10) return i / 2; + + // Will never be reached + return -1; } print f(5); diff --git a/src/Perlang.Tests.Integration/IndexOperator/DictionaryIndexing.cs b/src/Perlang.Tests.Integration/IndexOperator/DictionaryIndexing.cs index 5ad950a8..692a4f09 100644 --- a/src/Perlang.Tests.Integration/IndexOperator/DictionaryIndexing.cs +++ b/src/Perlang.Tests.Integration/IndexOperator/DictionaryIndexing.cs @@ -12,7 +12,7 @@ public class DictionaryIndexing private const string PathKey = "PATH"; #endif - [Fact] + [SkippableFact] public void dictionary_with_string_key_can_be_indexed_by_string() { string source = @$" @@ -32,7 +32,7 @@ public void dictionary_with_string_key_can_be_indexed_by_string() #endif } - [Fact] + [SkippableFact] public void dictionary_with_string_key_can_be_indexed_multiple_times() { string source = @$" diff --git a/src/Perlang.Tests.Integration/LogicalOperator/AmbiguousCombinationOfOperators.cs b/src/Perlang.Tests.Integration/LogicalOperator/AmbiguousCombinationOfOperators.cs index 7704799b..3dace104 100644 --- a/src/Perlang.Tests.Integration/LogicalOperator/AmbiguousCombinationOfOperators.cs +++ b/src/Perlang.Tests.Integration/LogicalOperator/AmbiguousCombinationOfOperators.cs @@ -1,4 +1,5 @@ using System.Linq; +using FluentAssertions; using Xunit; using static Perlang.Tests.Integration.EvalHelper; @@ -61,7 +62,9 @@ public void grouped_and_and_or_operators_does_not_emit_warnings() string output = EvalReturningOutput(source).SingleOrDefault(); - Assert.Equal("False", output); + // "False" in interpreted mode and "false" in compiled mode + output!.ToLower().Should() + .Be("false"); } } } diff --git a/src/Perlang.Tests.Integration/LogicalOperator/And.cs b/src/Perlang.Tests.Integration/LogicalOperator/And.cs index 438a4d4b..baaec75d 100644 --- a/src/Perlang.Tests.Integration/LogicalOperator/And.cs +++ b/src/Perlang.Tests.Integration/LogicalOperator/And.cs @@ -1,4 +1,5 @@ using System.Linq; +using FluentAssertions; using Xunit; using static Perlang.Tests.Integration.EvalHelper; @@ -28,12 +29,14 @@ public void short_circuits_at_the_first_falsy_argument() print b; "; - var output = EvalReturningOutput(source); + // "True" and "False" in interpreted mode, "true" and "false" in compiled mode + var output = EvalReturningOutput(source) + .Select(s => s.ToLower()); Assert.Equal(new[] { - "True", - "False" // The `a = "bad" assignment should never execute + "true", + "false" // The `a = "bad" assignment should never execute }, output); } @@ -47,7 +50,9 @@ public void true_is_truthy() string output = EvalReturningOutput(source).SingleOrDefault(); - Assert.Equal("True", output); + // "True" in interpreted mode and "true" in compiled mode + output!.ToLower().Should() + .Be("true"); } [Fact] @@ -61,7 +66,9 @@ public void false_is_falsy() string output = EvalReturningOutput(source).SingleOrDefault(); - Assert.Equal("False", output); + // "False" in interpreted mode and "false" in compiled mode + output!.ToLower().Should() + .Be("false"); } [Fact] @@ -106,7 +113,7 @@ public void string_is_not_a_valid_and_operand() Assert.Matches("'AsciiString' is not a valid && operand", exception.Message); } - [Fact] + [SkippableFact] public void result_of_and_is_boolean() { string source = @" diff --git a/src/Perlang.Tests.Integration/LogicalOperator/Or.cs b/src/Perlang.Tests.Integration/LogicalOperator/Or.cs index 4fdce47b..c6563d55 100644 --- a/src/Perlang.Tests.Integration/LogicalOperator/Or.cs +++ b/src/Perlang.Tests.Integration/LogicalOperator/Or.cs @@ -1,4 +1,5 @@ using System.Linq; +using FluentAssertions; using Xunit; using static Perlang.Tests.Integration.EvalHelper; @@ -28,12 +29,14 @@ public void short_circuits_at_the_first_true_argument() print b; "; - var output = EvalReturningOutput(source); + // "False" and "True" in interpreted mode, "false" and "true" in compiled mode + var output = EvalReturningOutput(source) + .Select(s => s.ToLower()); Assert.Equal(new[] { - "False", // The `a = true` assignment should never execute - "True" + "false", // The `a = true` assignment should never execute + "true" }, output); } @@ -47,7 +50,9 @@ public void true_is_truthy() string output = EvalReturningOutput(source).SingleOrDefault(); - Assert.Equal("True", output); + // "True" in interpreted mode and "true" in compiled mode + output!.ToLower().Should() + .Be("true"); } [Fact] @@ -61,7 +66,9 @@ public void false_is_falsy() string output = EvalReturningOutput(source).SingleOrDefault(); - Assert.Equal("False", output); + // "False" in interpreted mode and "false" in compiled mode + output!.ToLower().Should() + .Be("false"); } [Fact] @@ -106,7 +113,7 @@ public void string_is_not_a_valid_and_operand() Assert.Matches("'string' is not a valid || operand", exception.Message); } - [Fact] + [SkippableFact] public void result_of_or_is_boolean() { string source = @" diff --git a/src/Perlang.Tests.Integration/Number/NumberTests.cs b/src/Perlang.Tests.Integration/Number/NumberTests.cs index 1cc5ea1c..e91e165b 100644 --- a/src/Perlang.Tests.Integration/Number/NumberTests.cs +++ b/src/Perlang.Tests.Integration/Number/NumberTests.cs @@ -55,7 +55,7 @@ public void leading_dot() Assert.Matches("Expect expression", exception.Message); } - [Fact] + [SkippableFact] public void literal_integer() { string source = @" @@ -67,7 +67,7 @@ public void literal_integer() Assert.Equal(123, result); } - [Fact] + [SkippableFact] public void literal_integer_with_underscores() { string source = @" @@ -79,7 +79,7 @@ public void literal_integer_with_underscores() Assert.Equal(123456, result); } - [Fact] + [SkippableFact] public void literal_zero() { string source = @" @@ -91,7 +91,7 @@ public void literal_zero() Assert.Equal(0, result); } - [Fact] + [SkippableFact] public void literal_negative_zero() { string source = @" @@ -103,7 +103,7 @@ public void literal_negative_zero() Assert.Equal(-0, result); } - [Fact] + [SkippableFact] public void literal_negative_integer() { string source = @" @@ -115,7 +115,7 @@ public void literal_negative_integer() Assert.Equal(-123, result); } - [Fact] + [SkippableFact] public void literal_negative_larger_integer() { string source = @" @@ -127,7 +127,7 @@ public void literal_negative_larger_integer() Assert.Equal(-2147483648, result); } - [Theory] + [SkippableTheory] [ClassData(typeof(TestCultures))] public async Task literal_float(CultureInfo cultureInfo) { @@ -143,7 +143,7 @@ public async Task literal_float(CultureInfo cultureInfo) .Be(123.456f); } - [Theory] + [SkippableTheory] [ClassData(typeof(TestCultures))] public async Task literal_float_has_expected_type(CultureInfo cultureInfo) { @@ -159,7 +159,7 @@ public async Task literal_float_has_expected_type(CultureInfo cultureInfo) .BeOfType(); } - [Theory] + [SkippableTheory] [ClassData(typeof(TestCultures))] public async Task literal_negative_float(CultureInfo cultureInfo) { @@ -175,7 +175,7 @@ public async Task literal_negative_float(CultureInfo cultureInfo) .Be(-0.001f); } - [Theory] + [SkippableTheory] [ClassData(typeof(TestCultures))] public async Task literal_float_with_underscore_in_integer_part(CultureInfo cultureInfo) { @@ -191,7 +191,7 @@ public async Task literal_float_with_underscore_in_integer_part(CultureInfo cult .Be(12345.678f); } - [Theory] + [SkippableTheory] [ClassData(typeof(TestCultures))] public async Task literal_float_with_underscore_in_fractional_part(CultureInfo cultureInfo) { @@ -207,7 +207,7 @@ public async Task literal_float_with_underscore_in_fractional_part(CultureInfo c .Be(123.45678f); } - [Theory] + [SkippableTheory] [ClassData(typeof(TestCultures))] public async Task literal_double_with_suffix(CultureInfo cultureInfo) { @@ -223,7 +223,7 @@ public async Task literal_double_with_suffix(CultureInfo cultureInfo) .Be(123.456d); } - [Theory] + [SkippableTheory] [ClassData(typeof(TestCultures))] public async Task literal_double_with_suffix_has_expected_type(CultureInfo cultureInfo) { @@ -239,7 +239,7 @@ public async Task literal_double_with_suffix_has_expected_type(CultureInfo cultu .BeOfType(); } - [Theory] + [SkippableTheory] [ClassData(typeof(TestCultures))] public async Task literal_double_with_implicit_suffix(CultureInfo cultureInfo) { @@ -255,7 +255,7 @@ public async Task literal_double_with_implicit_suffix(CultureInfo cultureInfo) .Be(123.456d); } - [Theory] + [SkippableTheory] [ClassData(typeof(TestCultures))] public async Task literal_double_with_implicit_suffix_has_expected_type(CultureInfo cultureInfo) { @@ -271,7 +271,7 @@ public async Task literal_double_with_implicit_suffix_has_expected_type(CultureI .BeOfType(); } - [Fact] + [SkippableFact] public void literal_binary() { string source = @" @@ -283,7 +283,7 @@ public void literal_binary() Assert.Equal(42, result); } - [Fact] + [SkippableFact] public void literal_binary_with_underscores() { string source = @" @@ -295,7 +295,7 @@ public void literal_binary_with_underscores() Assert.Equal(42, result); } - [Fact] + [SkippableFact] public void literal_octal() { string source = @" @@ -307,7 +307,7 @@ public void literal_octal() Assert.Equal(493, result); } - [Fact] + [SkippableFact] public void literal_hexadecimal() { string source = @" @@ -319,7 +319,7 @@ public void literal_hexadecimal() Assert.Equal(3234512922, result); } - [Fact] + [SkippableFact] public void literal_hexadecimal_with_underscores() { string source = @" diff --git a/src/Perlang.Tests.Integration/Operator/Binary/AdditionAssignmentTests.cs b/src/Perlang.Tests.Integration/Operator/Binary/AdditionAssignmentTests.cs index 6077b030..33017237 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/AdditionAssignmentTests.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/AdditionAssignmentTests.cs @@ -6,7 +6,7 @@ namespace Perlang.Tests.Integration.Operator.Binary { public class AdditionAssignmentTests { - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.AdditionAssignment_result), MemberType = typeof(BinaryOperatorData))] public void performs_addition_assignment(string i, string j, string expectedResult) { @@ -22,10 +22,12 @@ public void performs_addition_assignment(string i, string j, string expectedResu .Be(expectedResult); } - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.AdditionAssignment_type), MemberType = typeof(BinaryOperatorData))] public void with_supported_types_returns_expected_type(string i, string j, string expectedType) { + Skip.If(PerlangMode.ExperimentalCompilation, "Not supported in compiled mode"); + string source = $@" var i = {i}; print (i += {j}).get_type(); diff --git a/src/Perlang.Tests.Integration/Operator/Binary/AdditionTests.cs b/src/Perlang.Tests.Integration/Operator/Binary/AdditionTests.cs index 61fa62bd..3e24dba0 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/AdditionTests.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/AdditionTests.cs @@ -12,7 +12,7 @@ namespace Perlang.Tests.Integration.Operator.Binary { public class AdditionTests { - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.Addition_result), MemberType = typeof(BinaryOperatorData))] void performs_addition(string i, string j, string expectedResult) { @@ -29,10 +29,12 @@ void performs_addition(string i, string j, string expectedResult) .Be(expectedResult); } - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.Addition_type), MemberType = typeof(BinaryOperatorData))] void with_supported_types_returns_expected_type(string i, string j, string expectedType) { + Skip.If(PerlangMode.ExperimentalCompilation, "Not supported in compiled mode"); + string source = $@" print ({i} + {j}).get_type(); "; @@ -43,7 +45,7 @@ void with_supported_types_returns_expected_type(string i, string j, string expec .Be(expectedType); } - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.Addition_type), MemberType = typeof(BinaryOperatorData))] void local_variable_inference_returns_expected_type(string i, string j, string expectedType) { @@ -74,7 +76,7 @@ public void with_unsupported_types_emits_expected_error(string i, string j, stri .Message.Should().Match(expectedResult); } - [Fact] + [SkippableFact] void addition_of_strings_performs_concatenation() { string source = @" @@ -90,7 +92,7 @@ void addition_of_strings_performs_concatenation() .Be("foobar"); } - [Fact] + [SkippableFact] void addition_of_integer_and_string_coerces_number_to_string() { // Some interesting notes on how other languages deal with this: @@ -114,7 +116,7 @@ void addition_of_integer_and_string_coerces_number_to_string() .Be("123abc"); } - [Fact] + [SkippableFact] void addition_of_bigint_and_string_coerces_number_to_string() { string source = @" @@ -130,7 +132,7 @@ void addition_of_bigint_and_string_coerces_number_to_string() .Be("18446744073709551616xyz"); } - [Fact] + [SkippableFact] void addition_of_string_and_integer_coerces_number_to_string() { string source = @" @@ -146,7 +148,7 @@ void addition_of_string_and_integer_coerces_number_to_string() .Be("abc123"); } - [Fact] + [SkippableFact] void addition_of_string_and_bigint_coerces_number_to_string() { string source = @" @@ -162,7 +164,7 @@ void addition_of_string_and_bigint_coerces_number_to_string() .Be("abc18446744073709551616"); } - [Theory] + [SkippableTheory] [ClassData(typeof(TestCultures))] async Task addition_of_float_and_string_coerces_number_to_string(CultureInfo cultureInfo) { @@ -181,7 +183,7 @@ async Task addition_of_float_and_string_coerces_number_to_string(CultureInfo cul .Be("123.45abc"); } - [Theory] + [SkippableTheory] [ClassData(typeof(TestCultures))] async Task addition_of_string_and_float_coerces_number_to_string(CultureInfo cultureInfo) { diff --git a/src/Perlang.Tests.Integration/Operator/Binary/BinaryOperatorData.cs b/src/Perlang.Tests.Integration/Operator/Binary/BinaryOperatorData.cs index afbb0e69..ec79de7c 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/BinaryOperatorData.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/BinaryOperatorData.cs @@ -23,52 +23,52 @@ public static class BinaryOperatorData public static IEnumerable Greater => new List { - new object[] { "-12", "34.0", "False" }, - new object[] { "2147483647", "4294967295", "False" }, - new object[] { "2147483647", "18446744073709551616", "False" }, - new object[] { "2147483647", "9223372036854775807", "False" }, - new object[] { "2147483646", "2147483647", "False" }, - new object[] { "2147483647", "2147483646", "True" }, - new object[] { "2147483647", "2147483647", "False" }, - new object[] { "2147483647", "340282349999999991754788743781432688640.0f", "False" }, - new object[] { "2147483647", "12.0", "True" }, - new object[] { "4294967296", "9223372036854775807", "False" }, - new object[] { "4294967295", "33", "True" }, - new object[] { "4294967295", "4294967295", "False" }, - new object[] { "4294967295", "9223372036854775807", "False" }, - new object[] { "4294967295", "18446744073709551615", "False" }, - new object[] { "4294967295", "18446744073709551616", "False" }, - new object[] { "4294967295", "12.0", "True" }, - new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "False" }, - new object[] { "9223372036854775807", "2", "True" }, - new object[] { "9223372036854775807", "4294967295", "True" }, - new object[] { "9223372036854775807", "9223372036854775807", "False" }, - new object[] { "9223372036854775807", "18446744073709551616", "False" }, - new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "False" }, - new object[] { "9223372036854775807", "12.0", "True" }, - new object[] { "18446744073709551615", "4294967295", "True" }, - new object[] { "18446744073709551615", "18446744073709551615", "False" }, - new object[] { "18446744073709551615", "18446744073709551616", "False" }, - new object[] { "18446744073709551615", "340282349999999991754788743781432688640.0f", "False" }, - new object[] { "18446744073709551615", "12.0", "True" }, - new object[] { "18446744073709551616", "2", "True" }, - new object[] { "18446744073709551616", "4294967295", "True" }, - new object[] { "18446744073709551616", "9223372036854775807", "True" }, - new object[] { "18446744073709551616", "18446744073709551615", "True" }, - new object[] { "18446744073709551616", "18446744073709551616", "False" }, - new object[] { "340282349999999991754788743781432688640.0f", "2147483647", "True" }, - new object[] { "340282349999999991754788743781432688640.0f", "4294967295", "True" }, - new object[] { "340282349999999991754788743781432688640.0f", "9223372036854775807", "True" }, - new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551615", "True" }, - new object[] { "340282349999999991754788743781432688640.0f", "340282349999999991754788743781432688640.0f", "False" }, - new object[] { "340282349999999991754788743781432688640.0f", "12.0", "True" }, - new object[] { "12.0", "2147483647", "False" }, - new object[] { "12.0", "4294967295", "False" }, - new object[] { "12.0", "9223372036854775807", "False" }, - new object[] { "12.0", "18446744073709551615", "False" }, - new object[] { "12.0", "340282349999999991754788743781432688640.0f", "False" }, - new object[] { "12.0", "34.0", "False" }, - new object[] { "34.0", "33.0", "True" }, + new object[] { "-12", "34.0", "false" }, + new object[] { "2147483647", "4294967295", "false" }, + new object[] { "2147483647", "18446744073709551616", "false" }, + new object[] { "2147483647", "9223372036854775807", "false" }, + new object[] { "2147483646", "2147483647", "false" }, + new object[] { "2147483647", "2147483646", "true" }, + new object[] { "2147483647", "2147483647", "false" }, + new object[] { "2147483647", "340282349999999991754788743781432688640.0f", "false" }, + new object[] { "2147483647", "12.0", "true" }, + new object[] { "4294967296", "9223372036854775807", "false" }, + new object[] { "4294967295", "33", "true" }, + new object[] { "4294967295", "4294967295", "false" }, + new object[] { "4294967295", "9223372036854775807", "false" }, + new object[] { "4294967295", "18446744073709551615", "false" }, + new object[] { "4294967295", "18446744073709551616", "false" }, + new object[] { "4294967295", "12.0", "true" }, + new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "false" }, + new object[] { "9223372036854775807", "2", "true" }, + new object[] { "9223372036854775807", "4294967295", "true" }, + new object[] { "9223372036854775807", "9223372036854775807", "false" }, + new object[] { "9223372036854775807", "18446744073709551616", "false" }, + new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "false" }, + new object[] { "9223372036854775807", "12.0", "true" }, + new object[] { "18446744073709551615", "4294967295", "true" }, + new object[] { "18446744073709551615", "18446744073709551615", "false" }, + new object[] { "18446744073709551615", "18446744073709551616", "false" }, + new object[] { "18446744073709551615", "340282349999999991754788743781432688640.0f", "false" }, + new object[] { "18446744073709551615", "12.0", "true" }, + new object[] { "18446744073709551616", "2", "true" }, + new object[] { "18446744073709551616", "4294967295", "true" }, + new object[] { "18446744073709551616", "9223372036854775807", "true" }, + new object[] { "18446744073709551616", "18446744073709551615", "true" }, + new object[] { "18446744073709551616", "18446744073709551616", "false" }, + new object[] { "340282349999999991754788743781432688640.0f", "2147483647", "true" }, + new object[] { "340282349999999991754788743781432688640.0f", "4294967295", "true" }, + new object[] { "340282349999999991754788743781432688640.0f", "9223372036854775807", "true" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551615", "true" }, + new object[] { "340282349999999991754788743781432688640.0f", "340282349999999991754788743781432688640.0f", "false" }, + new object[] { "340282349999999991754788743781432688640.0f", "12.0", "true" }, + new object[] { "12.0", "2147483647", "false" }, + new object[] { "12.0", "4294967295", "false" }, + new object[] { "12.0", "9223372036854775807", "false" }, + new object[] { "12.0", "18446744073709551615", "false" }, + new object[] { "12.0", "340282349999999991754788743781432688640.0f", "false" }, + new object[] { "12.0", "34.0", "false" }, + new object[] { "34.0", "33.0", "true" }, }; public static IEnumerable Greater_unsupported_types => @@ -87,51 +87,51 @@ public static class BinaryOperatorData public static IEnumerable GreaterEqual => new List { - new object[] { "2147483647", "4294967295", "False" }, - new object[] { "2147483647", "18446744073709551616", "False" }, - new object[] { "2147483647", "9223372036854775807", "False" }, - new object[] { "-2147483648", "34.0", "False" }, - new object[] { "2147483646", "2147483647", "False" }, - new object[] { "2147483647", "2147483647", "True" }, - new object[] { "2147483647", "2147483646", "True" }, - new object[] { "2147483647", "340282349999999991754788743781432688640.0f", "False" }, - new object[] { "2147483647", "33.0", "True" }, - new object[] { "4294967295", "33", "True" }, - new object[] { "4294967295", "4294967295", "True" }, - new object[] { "4294967295", "9223372036854775807", "False" }, - new object[] { "4294967295", "18446744073709551615", "False" }, - new object[] { "4294967295", "18446744073709551616", "False" }, - new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "False" }, - new object[] { "4294967295", "12.0", "True" }, - new object[] { "9223372036854775807", "2", "True" }, - new object[] { "9223372036854775807", "4294967295", "True" }, - new object[] { "9223372036854775807", "9223372036854775807", "True" }, - new object[] { "9223372036854775807", "18446744073709551616", "False" }, - new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "False" }, - new object[] { "9223372036854775807", "12.0", "True" }, - new object[] { "18446744073709551615", "4294967295", "True" }, - new object[] { "18446744073709551615", "18446744073709551615", "True" }, - new object[] { "18446744073709551615", "18446744073709551616", "False" }, - new object[] { "18446744073709551615", "340282349999999991754788743781432688640.0f", "False" }, - new object[] { "18446744073709551615", "12.0", "True" }, - new object[] { "18446744073709551616", "2", "True" }, - new object[] { "18446744073709551616", "4294967295", "True" }, - new object[] { "18446744073709551616", "9223372036854775807", "True" }, - new object[] { "18446744073709551616", "18446744073709551615", "True" }, - new object[] { "18446744073709551616", "18446744073709551616", "True" }, - new object[] { "340282349999999991754788743781432688640.0f", "2147483647", "True" }, - new object[] { "340282349999999991754788743781432688640.0f", "4294967295", "True" }, - new object[] { "340282349999999991754788743781432688640.0f", "9223372036854775807", "True" }, - new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551615", "True" }, - new object[] { "340282349999999991754788743781432688640.0f", "340282349999999991754788743781432688640.0f", "True" }, - new object[] { "340282349999999991754788743781432688640.0f", "12.0", "True" }, - new object[] { "12.0", "34", "False" }, - new object[] { "12.0", "4294967295", "False" }, - new object[] { "12.0", "9223372036854775807", "False" }, - new object[] { "12.0", "18446744073709551615", "False" }, - new object[] { "12.0", "34.0", "False" }, - new object[] { "34.0", "33.0", "True" }, - new object[] { "12.0", "340282349999999991754788743781432688640.0f", "False" }, + new object[] { "2147483647", "4294967295", "false" }, + new object[] { "2147483647", "18446744073709551616", "false" }, + new object[] { "2147483647", "9223372036854775807", "false" }, + new object[] { "-2147483648", "34.0", "false" }, + new object[] { "2147483646", "2147483647", "false" }, + new object[] { "2147483647", "2147483647", "true" }, + new object[] { "2147483647", "2147483646", "true" }, + new object[] { "2147483647", "340282349999999991754788743781432688640.0f", "false" }, + new object[] { "2147483647", "33.0", "true" }, + new object[] { "4294967295", "33", "true" }, + new object[] { "4294967295", "4294967295", "true" }, + new object[] { "4294967295", "9223372036854775807", "false" }, + new object[] { "4294967295", "18446744073709551615", "false" }, + new object[] { "4294967295", "18446744073709551616", "false" }, + new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "false" }, + new object[] { "4294967295", "12.0", "true" }, + new object[] { "9223372036854775807", "2", "true" }, + new object[] { "9223372036854775807", "4294967295", "true" }, + new object[] { "9223372036854775807", "9223372036854775807", "true" }, + new object[] { "9223372036854775807", "18446744073709551616", "false" }, + new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "false" }, + new object[] { "9223372036854775807", "12.0", "true" }, + new object[] { "18446744073709551615", "4294967295", "true" }, + new object[] { "18446744073709551615", "18446744073709551615", "true" }, + new object[] { "18446744073709551615", "18446744073709551616", "false" }, + new object[] { "18446744073709551615", "340282349999999991754788743781432688640.0f", "false" }, + new object[] { "18446744073709551615", "12.0", "true" }, + new object[] { "18446744073709551616", "2", "true" }, + new object[] { "18446744073709551616", "4294967295", "true" }, + new object[] { "18446744073709551616", "9223372036854775807", "true" }, + new object[] { "18446744073709551616", "18446744073709551615", "true" }, + new object[] { "18446744073709551616", "18446744073709551616", "true" }, + new object[] { "340282349999999991754788743781432688640.0f", "2147483647", "true" }, + new object[] { "340282349999999991754788743781432688640.0f", "4294967295", "true" }, + new object[] { "340282349999999991754788743781432688640.0f", "9223372036854775807", "true" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551615", "true" }, + new object[] { "340282349999999991754788743781432688640.0f", "340282349999999991754788743781432688640.0f", "true" }, + new object[] { "340282349999999991754788743781432688640.0f", "12.0", "true" }, + new object[] { "12.0", "34", "false" }, + new object[] { "12.0", "4294967295", "false" }, + new object[] { "12.0", "9223372036854775807", "false" }, + new object[] { "12.0", "18446744073709551615", "false" }, + new object[] { "12.0", "34.0", "false" }, + new object[] { "34.0", "33.0", "true" }, + new object[] { "12.0", "340282349999999991754788743781432688640.0f", "false" }, }; public static IEnumerable GreaterEqual_unsupported_types => @@ -150,56 +150,56 @@ public static class BinaryOperatorData public static IEnumerable Less => new List { - new object[] { "2", "34", "True" }, - new object[] { "2", "4294967295", "True" }, - new object[] { "2", "9223372036854775807", "True" }, - new object[] { "2", "18446744073709551616", "True" }, - new object[] { "2", "-34", "False" }, - new object[] { "2", "34.0", "True" }, - new object[] { "-12", "34", "True" }, - new object[] { "-12", "-34", "False" }, - new object[] { "-12", "-34.0", "False" }, - new object[] { "2147483647", "33.0", "False" }, - new object[] { "2147483647", "340282349999999991754788743781432688640.0f", "True" }, - new object[] { "4294967295", "33", "False" }, - new object[] { "4294967295", "4294967295", "False" }, - new object[] { "4294967295", "9223372036854775807", "True" }, - new object[] { "4294967295", "18446744073709551615", "True" }, - new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "True" }, - new object[] { "4294967295", "18446744073709551616", "True" }, - new object[] { "4294967295", "12.0", "False" }, - new object[] { "9223372036854775807", "2", "False" }, - new object[] { "9223372036854775807", "4294967295", "False" }, - new object[] { "9223372036854775807", "9223372036854775807", "False" }, - new object[] { "9223372036854775807", "18446744073709551616", "True" }, - new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "True" }, - new object[] { "9223372036854775807", "12.0", "False" }, - new object[] { "18446744073709551615", "4294967295", "False" }, - new object[] { "18446744073709551615", "18446744073709551615", "False" }, - new object[] { "18446744073709551615", "18446744073709551616", "True" }, - new object[] { "18446744073709551615", "340282349999999991754788743781432688640.0f", "True" }, - new object[] { "18446744073709551615", "12.0", "False" }, - new object[] { "18446744073709551616", "2", "False" }, - new object[] { "18446744073709551616", "4294967295", "False" }, - new object[] { "18446744073709551616", "9223372036854775807", "False" }, - new object[] { "18446744073709551616", "18446744073709551615", "False" }, - new object[] { "18446744073709551616", "18446744073709551616", "False" }, - new object[] { "18446744073709551616", "18446744073709551617", "True" }, - new object[] { "12.0", "-34", "False" }, - new object[] { "-12.0", "-34", "False" }, - new object[] { "12.0", "4294967295", "True" }, - new object[] { "12.0", "9223372036854775807", "True" }, - new object[] { "-12.0", "9223372036854775807", "True" }, - new object[] { "12.0", "18446744073709551615", "True" }, - new object[] { "12.0", "340282349999999991754788743781432688640.0f", "True" }, - new object[] { "340282349999999991754788743781432688640.0f", "2147483647", "False" }, - new object[] { "340282349999999991754788743781432688640.0f", "4294967295", "False" }, - new object[] { "340282349999999991754788743781432688640.0f", "9223372036854775807", "False" }, - new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551615", "False" }, - new object[] { "340282349999999991754788743781432688640.0f", "340282349999999991754788743781432688640.0f", "False" }, - new object[] { "340282349999999991754788743781432688640.0f", "12.0", "False" }, - new object[] { "34.0", "33.0", "False" }, - new object[] { "12.0", "34.0", "True" }, + new object[] { "2", "34", "true" }, + new object[] { "2", "4294967295", "true" }, + new object[] { "2", "9223372036854775807", "true" }, + new object[] { "2", "18446744073709551616", "true" }, + new object[] { "2", "-34", "false" }, + new object[] { "2", "34.0", "true" }, + new object[] { "-12", "34", "true" }, + new object[] { "-12", "-34", "false" }, + new object[] { "-12", "-34.0", "false" }, + new object[] { "2147483647", "33.0", "false" }, + new object[] { "2147483647", "340282349999999991754788743781432688640.0f", "true" }, + new object[] { "4294967295", "33", "false" }, + new object[] { "4294967295", "4294967295", "false" }, + new object[] { "4294967295", "9223372036854775807", "true" }, + new object[] { "4294967295", "18446744073709551615", "true" }, + new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "true" }, + new object[] { "4294967295", "18446744073709551616", "true" }, + new object[] { "4294967295", "12.0", "false" }, + new object[] { "9223372036854775807", "2", "false" }, + new object[] { "9223372036854775807", "4294967295", "false" }, + new object[] { "9223372036854775807", "9223372036854775807", "false" }, + new object[] { "9223372036854775807", "18446744073709551616", "true" }, + new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "true" }, + new object[] { "9223372036854775807", "12.0", "false" }, + new object[] { "18446744073709551615", "4294967295", "false" }, + new object[] { "18446744073709551615", "18446744073709551615", "false" }, + new object[] { "18446744073709551615", "18446744073709551616", "true" }, + new object[] { "18446744073709551615", "340282349999999991754788743781432688640.0f", "true" }, + new object[] { "18446744073709551615", "12.0", "false" }, + new object[] { "18446744073709551616", "2", "false" }, + new object[] { "18446744073709551616", "4294967295", "false" }, + new object[] { "18446744073709551616", "9223372036854775807", "false" }, + new object[] { "18446744073709551616", "18446744073709551615", "false" }, + new object[] { "18446744073709551616", "18446744073709551616", "false" }, + new object[] { "18446744073709551616", "18446744073709551617", "true" }, + new object[] { "12.0", "-34", "false" }, + new object[] { "-12.0", "-34", "false" }, + new object[] { "12.0", "4294967295", "true" }, + new object[] { "12.0", "9223372036854775807", "true" }, + new object[] { "-12.0", "9223372036854775807", "true" }, + new object[] { "12.0", "18446744073709551615", "true" }, + new object[] { "12.0", "340282349999999991754788743781432688640.0f", "true" }, + new object[] { "340282349999999991754788743781432688640.0f", "2147483647", "false" }, + new object[] { "340282349999999991754788743781432688640.0f", "4294967295", "false" }, + new object[] { "340282349999999991754788743781432688640.0f", "9223372036854775807", "false" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551615", "false" }, + new object[] { "340282349999999991754788743781432688640.0f", "340282349999999991754788743781432688640.0f", "false" }, + new object[] { "340282349999999991754788743781432688640.0f", "12.0", "false" }, + new object[] { "34.0", "33.0", "false" }, + new object[] { "12.0", "34.0", "true" }, }; public static IEnumerable Less_unsupported_types => @@ -218,52 +218,52 @@ public static class BinaryOperatorData public static IEnumerable LessEqual => new List { - new object[] { "-2147483648", "4294967295", "True" }, - new object[] { "2147483646", "2147483647", "True" }, - new object[] { "2147483647", "4294967295", "True" }, - new object[] { "2147483647", "18446744073709551616", "True" }, - new object[] { "2147483647", "9223372036854775807", "True" }, - new object[] { "2147483647", "2147483647", "True" }, - new object[] { "2147483647", "2147483646", "False" }, - new object[] { "2147483647", "33.0", "False" }, - new object[] { "2147483647", "340282349999999991754788743781432688640.0f", "True" }, - new object[] { "4294967295", "33", "False" }, - new object[] { "4294967295", "4294967295", "True" }, - new object[] { "4294967295", "9223372036854775807", "True" }, - new object[] { "4294967295", "18446744073709551615", "True" }, - new object[] { "4294967295", "18446744073709551616", "True" }, - new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "True" }, - new object[] { "4294967295", "12.0", "False" }, - new object[] { "9223372036854775807", "2", "False" }, - new object[] { "9223372036854775807", "4294967295", "False" }, - new object[] { "9223372036854775807", "9223372036854775807", "True" }, - new object[] { "9223372036854775807", "18446744073709551616", "True" }, - new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "True" }, - new object[] { "9223372036854775807", "12.0", "False" }, - new object[] { "18446744073709551615", "4294967295", "False" }, - new object[] { "18446744073709551615", "18446744073709551615", "True" }, - new object[] { "18446744073709551615", "18446744073709551616", "True" }, - new object[] { "18446744073709551615", "340282349999999991754788743781432688640.0f", "True" }, - new object[] { "18446744073709551615", "12.0", "False" }, - new object[] { "18446744073709551616", "2", "False" }, - new object[] { "18446744073709551616", "4294967295", "False" }, - new object[] { "18446744073709551616", "9223372036854775807", "False" }, - new object[] { "18446744073709551616", "18446744073709551615", "False" }, - new object[] { "18446744073709551616", "18446744073709551616", "True" }, - new object[] { "340282349999999991754788743781432688640.0f", "2147483647", "False" }, - new object[] { "340282349999999991754788743781432688640.0f", "4294967295", "False" }, - new object[] { "340282349999999991754788743781432688640.0f", "9223372036854775807", "False" }, - new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551615", "False" }, - new object[] { "340282349999999991754788743781432688640.0f", "340282349999999991754788743781432688640.0f", "True" }, - new object[] { "340282349999999991754788743781432688640.0f", "34.0", "False" }, - new object[] { "12.0", "34.0", "True" }, - new object[] { "12.0", "-34", "False" }, - new object[] { "12.0", "4294967295", "True" }, - new object[] { "12.0", "9223372036854775807", "True" }, - new object[] { "12.0", "18446744073709551615", "True" }, - new object[] { "12.0", "34.0", "True" }, - new object[] { "34.0", "33.0", "False" }, - new object[] { "34.0", "340282349999999991754788743781432688640.0f", "True" }, + new object[] { "-2147483648", "4294967295", "true" }, + new object[] { "2147483646", "2147483647", "true" }, + new object[] { "2147483647", "4294967295", "true" }, + new object[] { "2147483647", "18446744073709551616", "true" }, + new object[] { "2147483647", "9223372036854775807", "true" }, + new object[] { "2147483647", "2147483647", "true" }, + new object[] { "2147483647", "2147483646", "false" }, + new object[] { "2147483647", "33.0", "false" }, + new object[] { "2147483647", "340282349999999991754788743781432688640.0f", "true" }, + new object[] { "4294967295", "33", "false" }, + new object[] { "4294967295", "4294967295", "true" }, + new object[] { "4294967295", "9223372036854775807", "true" }, + new object[] { "4294967295", "18446744073709551615", "true" }, + new object[] { "4294967295", "18446744073709551616", "true" }, + new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "true" }, + new object[] { "4294967295", "12.0", "false" }, + new object[] { "9223372036854775807", "2", "false" }, + new object[] { "9223372036854775807", "4294967295", "false" }, + new object[] { "9223372036854775807", "9223372036854775807", "true" }, + new object[] { "9223372036854775807", "18446744073709551616", "true" }, + new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "true" }, + new object[] { "9223372036854775807", "12.0", "false" }, + new object[] { "18446744073709551615", "4294967295", "false" }, + new object[] { "18446744073709551615", "18446744073709551615", "true" }, + new object[] { "18446744073709551615", "18446744073709551616", "true" }, + new object[] { "18446744073709551615", "340282349999999991754788743781432688640.0f", "true" }, + new object[] { "18446744073709551615", "12.0", "false" }, + new object[] { "18446744073709551616", "2", "false" }, + new object[] { "18446744073709551616", "4294967295", "false" }, + new object[] { "18446744073709551616", "9223372036854775807", "false" }, + new object[] { "18446744073709551616", "18446744073709551615", "false" }, + new object[] { "18446744073709551616", "18446744073709551616", "true" }, + new object[] { "340282349999999991754788743781432688640.0f", "2147483647", "false" }, + new object[] { "340282349999999991754788743781432688640.0f", "4294967295", "false" }, + new object[] { "340282349999999991754788743781432688640.0f", "9223372036854775807", "false" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551615", "false" }, + new object[] { "340282349999999991754788743781432688640.0f", "340282349999999991754788743781432688640.0f", "true" }, + new object[] { "340282349999999991754788743781432688640.0f", "34.0", "false" }, + new object[] { "12.0", "34.0", "true" }, + new object[] { "12.0", "-34", "false" }, + new object[] { "12.0", "4294967295", "true" }, + new object[] { "12.0", "9223372036854775807", "true" }, + new object[] { "12.0", "18446744073709551615", "true" }, + new object[] { "12.0", "34.0", "true" }, + new object[] { "34.0", "33.0", "false" }, + new object[] { "34.0", "340282349999999991754788743781432688640.0f", "true" }, }; public static IEnumerable LessEqual_unsupported_types => @@ -282,115 +282,115 @@ public static class BinaryOperatorData public static IEnumerable NotEqual => new List { - new object[] { "12", "34", "True" }, - new object[] { "12", "12", "False" }, - new object[] { "12", "12.0", "True" }, // Same value but different types. Note: this is truthy in C# AND Java. - new object[] { "12.0", "12", "True" }, // Same value but different types. Note: this is truthy in C# AND Java. + new object[] { "12", "34", "true" }, + new object[] { "12", "12", "false" }, + new object[] { "12", "12.0", "true" }, // Same value but different types. Note: this is truthy in C# AND Java. + new object[] { "12.0", "12", "true" }, // Same value but different types. Note: this is truthy in C# AND Java. - new object[] { "2147483647", "4294967295", "True" }, - new object[] { "2147483647", "9223372036854775807", "True" }, - new object[] { "2147483647", "18446744073709551615", "True" }, - new object[] { "2147483647", "18446744073709551616", "True" }, - new object[] { "2147483647", "340282349999999991754788743781432688640.0f", "True" }, - new object[] { "4294967295", "33", "True" }, - new object[] { "4294967295", "4294967295", "False" }, - new object[] { "4294967295", "9223372036854775807", "True" }, - new object[] { "4294967295", "18446744073709551615", "True" }, - new object[] { "4294967295", "18446744073709551616", "True" }, - new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "True" }, - new object[] { "4294967295", "12.0", "True" }, - new object[] { "9223372036854775807", "2", "True" }, - new object[] { "9223372036854775807", "4294967295", "True" }, - new object[] { "9223372036854775807", "9223372036854775807", "False" }, - new object[] { "9223372036854775807", "18446744073709551615", "True" }, - new object[] { "9223372036854775807", "18446744073709551616", "True" }, - new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "True" }, - new object[] { "9223372036854775807", "12.0", "True" }, - new object[] { "18446744073709551615", "2", "True" }, - new object[] { "18446744073709551615", "4294967295", "True" }, - new object[] { "18446744073709551615", "9223372036854775807", "True" }, - new object[] { "18446744073709551615", "18446744073709551615", "False" }, - new object[] { "18446744073709551615", "18446744073709551616", "True" }, - new object[] { "18446744073709551615", "340282349999999991754788743781432688640.0f", "True" }, - new object[] { "18446744073709551615", "12.0", "True" }, - new object[] { "18446744073709551616", "2", "True" }, - new object[] { "18446744073709551616", "4294967295", "True" }, - new object[] { "18446744073709551616", "9223372036854775807", "True" }, - new object[] { "18446744073709551616", "18446744073709551615", "True" }, - new object[] { "18446744073709551616", "18446744073709551616", "False" }, - new object[] { "18446744073709551616", "340282349999999991754788743781432688640.0f", "True" }, - new object[] { "18446744073709551616", "12.0", "True" }, - new object[] { "340282349999999991754788743781432688640.0f", "2147483647", "True" }, - new object[] { "340282349999999991754788743781432688640.0f", "4294967295", "True" }, - new object[] { "340282349999999991754788743781432688640.0f", "9223372036854775807", "True" }, - new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551615", "True" }, - new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551616", "True" }, - new object[] { "340282349999999991754788743781432688640.0f", "340282349999999991754788743781432688640.0f", "False" }, - new object[] { "340282349999999991754788743781432688640.0f", "12.0", "True" }, - new object[] { "-12.0", "4294967295", "True" }, - new object[] { "-12.0", "9223372036854775807", "True" }, - new object[] { "-12.0", "18446744073709551615", "True" }, - new object[] { "-12.0", "18446744073709551616", "True" }, - new object[] { "-12.0", "340282349999999991754788743781432688640.0f", "True" }, - new object[] { "12.345", "12.345", "False" }, - new object[] { "12.345", "67.890", "True" }, + new object[] { "2147483647", "4294967295", "true" }, + new object[] { "2147483647", "9223372036854775807", "true" }, + new object[] { "2147483647", "18446744073709551615", "true" }, + new object[] { "2147483647", "18446744073709551616", "true" }, + new object[] { "2147483647", "340282349999999991754788743781432688640.0f", "true" }, + new object[] { "4294967295", "33", "true" }, + new object[] { "4294967295", "4294967295", "false" }, + new object[] { "4294967295", "9223372036854775807", "true" }, + new object[] { "4294967295", "18446744073709551615", "true" }, + new object[] { "4294967295", "18446744073709551616", "true" }, + new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "true" }, + new object[] { "4294967295", "12.0", "true" }, + new object[] { "9223372036854775807", "2", "true" }, + new object[] { "9223372036854775807", "4294967295", "true" }, + new object[] { "9223372036854775807", "9223372036854775807", "false" }, + new object[] { "9223372036854775807", "18446744073709551615", "true" }, + new object[] { "9223372036854775807", "18446744073709551616", "true" }, + new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "true" }, + new object[] { "9223372036854775807", "12.0", "true" }, + new object[] { "18446744073709551615", "2", "true" }, + new object[] { "18446744073709551615", "4294967295", "true" }, + new object[] { "18446744073709551615", "9223372036854775807", "true" }, + new object[] { "18446744073709551615", "18446744073709551615", "false" }, + new object[] { "18446744073709551615", "18446744073709551616", "true" }, + new object[] { "18446744073709551615", "340282349999999991754788743781432688640.0f", "true" }, + new object[] { "18446744073709551615", "12.0", "true" }, + new object[] { "18446744073709551616", "2", "true" }, + new object[] { "18446744073709551616", "4294967295", "true" }, + new object[] { "18446744073709551616", "9223372036854775807", "true" }, + new object[] { "18446744073709551616", "18446744073709551615", "true" }, + new object[] { "18446744073709551616", "18446744073709551616", "false" }, + new object[] { "18446744073709551616", "340282349999999991754788743781432688640.0f", "true" }, + new object[] { "18446744073709551616", "12.0", "true" }, + new object[] { "340282349999999991754788743781432688640.0f", "2147483647", "true" }, + new object[] { "340282349999999991754788743781432688640.0f", "4294967295", "true" }, + new object[] { "340282349999999991754788743781432688640.0f", "9223372036854775807", "true" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551615", "true" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551616", "true" }, + new object[] { "340282349999999991754788743781432688640.0f", "340282349999999991754788743781432688640.0f", "false" }, + new object[] { "340282349999999991754788743781432688640.0f", "12.0", "true" }, + new object[] { "-12.0", "4294967295", "true" }, + new object[] { "-12.0", "9223372036854775807", "true" }, + new object[] { "-12.0", "18446744073709551615", "true" }, + new object[] { "-12.0", "18446744073709551616", "true" }, + new object[] { "-12.0", "340282349999999991754788743781432688640.0f", "true" }, + new object[] { "12.345", "12.345", "false" }, + new object[] { "12.345", "67.890", "true" }, }; public static IEnumerable Equal => new List { - new object[] { "12", "34", "False" }, - new object[] { "12", "12", "True" }, - new object[] { "12", "12.0", "False" }, // Same value but different types. Note: this is truthy in C# AND Java. - new object[] { "12.0", "12", "False" }, // Same value but different types. Note: this is truthy in C# AND Java. - new object[] { "12.345", "12.345", "True" }, - new object[] { "12.345", "67.890", "False" }, + new object[] { "12", "34", "false" }, + new object[] { "12", "12", "true" }, + new object[] { "12", "12.0", "false" }, // Same value but different types. Note: this is truthy in C# AND Java. + new object[] { "12.0", "12", "false" }, // Same value but different types. Note: this is truthy in C# AND Java. + new object[] { "12.345", "12.345", "true" }, + new object[] { "12.345", "67.890", "false" }, - new object[] { "2147483647", "4294967295", "False" }, - new object[] { "2147483647", "9223372036854775807", "False" }, - new object[] { "2147483647", "18446744073709551615", "False" }, - new object[] { "2147483647", "18446744073709551616", "False" }, - new object[] { "2147483647", "340282349999999991754788743781432688640.0f", "False" }, - new object[] { "4294967295", "33", "False" }, - new object[] { "4294967295", "4294967295", "True" }, - new object[] { "4294967295", "9223372036854775807", "False" }, - new object[] { "4294967295", "18446744073709551615", "False" }, - new object[] { "4294967295", "18446744073709551616", "False" }, - new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "False" }, - new object[] { "4294967295", "12.0", "False" }, - new object[] { "9223372036854775807", "2147483647", "False" }, - new object[] { "9223372036854775807", "4294967295", "False" }, - new object[] { "9223372036854775807", "9223372036854775807", "True" }, - new object[] { "9223372036854775807", "18446744073709551615", "False" }, - new object[] { "9223372036854775807", "18446744073709551616", "False" }, - new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "False" }, - new object[] { "9223372036854775807", "12.0", "False" }, - new object[] { "18446744073709551615", "2147483647", "False" }, - new object[] { "18446744073709551615", "4294967295", "False" }, - new object[] { "18446744073709551615", "9223372036854775807", "False" }, - new object[] { "18446744073709551615", "18446744073709551615", "True" }, - new object[] { "18446744073709551615", "18446744073709551616", "False" }, - new object[] { "18446744073709551615", "340282349999999991754788743781432688640.0f", "False" }, - new object[] { "18446744073709551615", "12.0", "False" }, - new object[] { "18446744073709551616", "2147483647", "False" }, - new object[] { "18446744073709551616", "4294967295", "False" }, - new object[] { "18446744073709551616", "9223372036854775807", "False" }, - new object[] { "18446744073709551616", "18446744073709551615", "False" }, - new object[] { "18446744073709551616", "18446744073709551616", "True" }, - new object[] { "18446744073709551616", "340282349999999991754788743781432688640.0f", "False" }, - new object[] { "18446744073709551616", "12.0", "False" }, - new object[] { "340282349999999991754788743781432688640.0f", "2147483647", "False" }, - new object[] { "340282349999999991754788743781432688640.0f", "4294967295", "False" }, - new object[] { "340282349999999991754788743781432688640.0f", "9223372036854775807", "False" }, - new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551615", "False" }, - new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551616", "False" }, - new object[] { "340282349999999991754788743781432688640.0f", "340282349999999991754788743781432688640.0f", "True" }, - new object[] { "340282349999999991754788743781432688640.0f", "12.0", "False" }, - new object[] { "-12.0", "4294967295", "False" }, - new object[] { "-12.0", "9223372036854775807", "False" }, - new object[] { "-12.0", "18446744073709551615", "False" }, - new object[] { "-12.0", "18446744073709551616", "False" }, - new object[] { "-12.0", "340282349999999991754788743781432688640.0f", "False" }, + new object[] { "2147483647", "4294967295", "false" }, + new object[] { "2147483647", "9223372036854775807", "false" }, + new object[] { "2147483647", "18446744073709551615", "false" }, + new object[] { "2147483647", "18446744073709551616", "false" }, + new object[] { "2147483647", "340282349999999991754788743781432688640.0f", "false" }, + new object[] { "4294967295", "33", "false" }, + new object[] { "4294967295", "4294967295", "true" }, + new object[] { "4294967295", "9223372036854775807", "false" }, + new object[] { "4294967295", "18446744073709551615", "false" }, + new object[] { "4294967295", "18446744073709551616", "false" }, + new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "false" }, + new object[] { "4294967295", "12.0", "false" }, + new object[] { "9223372036854775807", "2147483647", "false" }, + new object[] { "9223372036854775807", "4294967295", "false" }, + new object[] { "9223372036854775807", "9223372036854775807", "true" }, + new object[] { "9223372036854775807", "18446744073709551615", "false" }, + new object[] { "9223372036854775807", "18446744073709551616", "false" }, + new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "false" }, + new object[] { "9223372036854775807", "12.0", "false" }, + new object[] { "18446744073709551615", "2147483647", "false" }, + new object[] { "18446744073709551615", "4294967295", "false" }, + new object[] { "18446744073709551615", "9223372036854775807", "false" }, + new object[] { "18446744073709551615", "18446744073709551615", "true" }, + new object[] { "18446744073709551615", "18446744073709551616", "false" }, + new object[] { "18446744073709551615", "340282349999999991754788743781432688640.0f", "false" }, + new object[] { "18446744073709551615", "12.0", "false" }, + new object[] { "18446744073709551616", "2147483647", "false" }, + new object[] { "18446744073709551616", "4294967295", "false" }, + new object[] { "18446744073709551616", "9223372036854775807", "false" }, + new object[] { "18446744073709551616", "18446744073709551615", "false" }, + new object[] { "18446744073709551616", "18446744073709551616", "true" }, + new object[] { "18446744073709551616", "340282349999999991754788743781432688640.0f", "false" }, + new object[] { "18446744073709551616", "12.0", "false" }, + new object[] { "340282349999999991754788743781432688640.0f", "2147483647", "false" }, + new object[] { "340282349999999991754788743781432688640.0f", "4294967295", "false" }, + new object[] { "340282349999999991754788743781432688640.0f", "9223372036854775807", "false" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551615", "false" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551616", "false" }, + new object[] { "340282349999999991754788743781432688640.0f", "340282349999999991754788743781432688640.0f", "true" }, + new object[] { "340282349999999991754788743781432688640.0f", "12.0", "false" }, + new object[] { "-12.0", "4294967295", "false" }, + new object[] { "-12.0", "9223372036854775807", "false" }, + new object[] { "-12.0", "18446744073709551615", "false" }, + new object[] { "-12.0", "18446744073709551616", "false" }, + new object[] { "-12.0", "340282349999999991754788743781432688640.0f", "false" }, }; public static IEnumerable Subtraction_result => @@ -1023,7 +1023,7 @@ public static class BinaryOperatorData private static IEnumerable ShiftRight_result_and_type => new List { - new object[] { "2147483647", "32", "2147483647", typeof(int) }, + new object[] { "2147483647", "32", "2147483647", typeof(int) }, // Wraparound new object[] { "4294967295", "2", "1073741823", typeof(uint) }, new object[] { "9223372036854775807", "2", "2305843009213693951", typeof(long) }, new object[] { "18446744073709551615", "2", "4611686018427387903", typeof(ulong) }, diff --git a/src/Perlang.Tests.Integration/Operator/Binary/BinaryOperatorDataTests.cs b/src/Perlang.Tests.Integration/Operator/Binary/BinaryOperatorDataTests.cs index a822a305..9d4783c4 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/BinaryOperatorDataTests.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/BinaryOperatorDataTests.cs @@ -22,178 +22,178 @@ public BinaryOperatorDataTests() Formatter.AddFormatter(new HashSetFormatter()); } - [Fact] + [SkippableFact] void Greater_has_test_data_for_all_supported_primitive_types() { EnsureAllPrimitiveTypesAreHandled(Greater, Greater_unsupported_types, "Greater"); } - [Fact] + [SkippableFact] void Greater_has_test_data_for_all_primitive_type_pairs() { EnsureAllPrimitiveTypePairsAreHandled(Greater, "Greater"); } - [Fact] + [SkippableFact] void GreaterEqual_has_test_data_for_all_supported_primitive_types() { EnsureAllPrimitiveTypesAreHandled(GreaterEqual, GreaterEqual_unsupported_types, "GreaterEqual"); } - [Fact] + [SkippableFact] void GreaterEqual_has_test_data_for_all_primitive_type_pairs() { EnsureAllPrimitiveTypePairsAreHandled(GreaterEqual, "GreaterEqual"); } - [Fact] + [SkippableFact] void Less_has_test_data_for_all_supported_primitive_types() { EnsureAllPrimitiveTypesAreHandled(Less, Less_unsupported_types, "Less"); } - [Fact] + [SkippableFact] void Less_has_test_data_for_all_primitive_type_pairs() { EnsureAllPrimitiveTypePairsAreHandled(Less, "Less"); } - [Fact] + [SkippableFact] void LessEqual_has_test_data_for_all_supported_primitive_types() { EnsureAllPrimitiveTypesAreHandled(LessEqual, LessEqual_unsupported_types, "LessEqual"); } - [Fact] + [SkippableFact] void LessEqual_has_test_data_for_all_primitive_type_pairs() { EnsureAllPrimitiveTypePairsAreHandled(LessEqual, "LessEqual"); } - [Fact] + [SkippableFact] void NotEqual_has_test_data_for_all_supported_primitive_types() { EnsureAllPrimitiveTypesAreHandled(NotEqual, ImmutableList.Empty, "NotEqual"); } - [Fact] + [SkippableFact] void NotEqual_has_test_data_for_all_primitive_type_pairs() { EnsureAllPrimitiveTypePairsAreHandled(NotEqual, "NotEqual"); } - [Fact] + [SkippableFact] void Equal_has_test_data_for_all_supported_primitive_types() { EnsureAllPrimitiveTypesAreHandled(Equal, ImmutableList.Empty, "Equal"); } - [Fact] + [SkippableFact] void Equal_has_test_data_for_all_primitive_type_pairs() { EnsureAllPrimitiveTypePairsAreHandled(Equal, "Equal"); } - [Fact] + [SkippableFact] void Subtraction_has_test_data_for_all_supported_primitive_types() { EnsureAllPrimitiveTypesAreHandled(Subtraction_result, Subtraction_unsupported_types, "Subtraction"); } - [Fact] + [SkippableFact] void Subtraction_has_test_data_for_all_primitive_type_pairs() { EnsureAllPrimitiveTypePairsAreHandled(Subtraction_result, "Subtraction"); } - [Fact] + [SkippableFact] void SubtractionAssignment_has_test_data_for_all_supported_primitive_types() { EnsureAllPrimitiveTypesAreHandled(SubtractionAssignment_result, SubtractionAssignment_unsupported_types, "SubtractionAssignment"); } - [Fact] + [SkippableFact] void SubtractionAssignment_has_test_data_for_all_primitive_type_pairs() { EnsureAllPrimitiveTypePairsAreHandled(SubtractionAssignment_result, "SubtractionAssignment"); } - [Fact] + [SkippableFact] void Addition_has_test_data_for_all_supported_primitive_types() { EnsureAllPrimitiveTypesAreHandled(Addition_result, Addition_unsupported_types, "Addition"); } - [Fact] + [SkippableFact] void Addition_has_test_data_for_all_primitive_type_pairs() { EnsureAllPrimitiveTypePairsAreHandled(Addition_result, "Addition"); } - [Fact] + [SkippableFact] void AdditionAssignment_has_test_data_for_all_supported_primitive_types() { EnsureAllPrimitiveTypesAreHandled(AdditionAssignment_result, AdditionAssignment_unsupported_types, "AdditionAssignment"); } - [Fact] + [SkippableFact] void AdditionAssignment_has_test_data_for_all_primitive_type_pairs() { EnsureAllPrimitiveTypePairsAreHandled(AdditionAssignment_result, "AdditionAssignment"); } - [Fact] + [SkippableFact] void Division_has_test_data_for_all_supported_primitive_types() { EnsureAllPrimitiveTypesAreHandled(Division_result, Division_unsupported_types, "Division"); } - [Fact] + [SkippableFact] void Division_has_test_data_for_all_primitive_type_pairs() { EnsureAllPrimitiveTypePairsAreHandled(Division_result, "Division"); } - [Fact] + [SkippableFact] void Multiplication_has_test_data_for_all_supported_primitive_types() { EnsureAllPrimitiveTypesAreHandled(Multiplication_result, Multiplication_unsupported_types, "Multiplication"); } - [Fact] + [SkippableFact] void Multiplication_has_test_data_for_all_primitive_type_pairs() { EnsureAllPrimitiveTypePairsAreHandled(Multiplication_result, "Multiplication"); } - [Fact] + [SkippableFact] void Exponential_has_test_data_for_all_supported_primitive_types() { EnsureAllPrimitiveTypesAreHandled(Exponential_result, Exponential_unsupported_types, "Exponential"); } - // EnsureAllPrimitiveTypePairsAreHandled deliberately not tests for the exponential operator, since it only supports - // a limited subset of operand types. + // EnsureAllPrimitiveTypePairsAreHandled deliberately does not test for the exponential operator, since it only + // supports a limited subset of operand types. - [Fact] + [SkippableFact] void Modulo_has_test_data_for_all_supported_primitive_types() { EnsureAllPrimitiveTypesAreHandled(Modulo_result, Modulo_unsupported_types, "Modulo"); } - [Fact] + [SkippableFact] void Modulo_has_test_data_for_all_primitive_type_pairs() { EnsureAllPrimitiveTypePairsAreHandled(Modulo_result, "Modulo"); } - [Fact] + [SkippableFact] void ShiftLeft_has_test_data_for_all_supported_primitive_types() { EnsureAllPrimitiveTypesAreHandled(ShiftLeft_result, ShiftLeft_unsupported_types, "ShiftLeft"); } - [Fact] + [SkippableFact] void ShiftRight_has_test_data_for_all_supported_primitive_types() { EnsureAllPrimitiveTypesAreHandled(ShiftRight_result, ShiftRight_unsupported_types, "ShiftRight"); diff --git a/src/Perlang.Tests.Integration/Operator/Binary/Comparison.cs b/src/Perlang.Tests.Integration/Operator/Binary/Comparison.cs index df6bcdc7..3d80ad60 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/Comparison.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/Comparison.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Linq; using System.Threading.Tasks; +using FluentAssertions; using Xunit; using static Perlang.Tests.Integration.EvalHelper; @@ -35,7 +36,7 @@ public class Comparison // Tests for the < (less than) operator // - [Theory] + [SkippableTheory] [MemberData(nameof(ComparisonTypes))] public void less_than_greater_is_true(string left, string right) { @@ -46,12 +47,14 @@ public void less_than_greater_is_true(string left, string right) print b; "; - string output = EvalReturningOutput(source).SingleOrDefault(); + // "True" vs "true" in interpreted and compiled mode + string output = EvalReturningOutput(source).SingleOrDefault()! + .ToLower(); - Assert.Equal("True", output); + Assert.Equal("true", output); } - [Theory] + [SkippableTheory] [MemberData(nameof(ComparisonTypes))] public void less_than_same_is_false(string left, string right) { @@ -62,12 +65,14 @@ public void less_than_same_is_false(string left, string right) print b; "; - string output = EvalReturningOutput(source).SingleOrDefault(); + // "False" vs "false" in interpreted and compiled mode + string output = EvalReturningOutput(source).SingleOrDefault()! + .ToLower(); - Assert.Equal("False", output); + Assert.Equal("false", output); } - [Theory] + [SkippableTheory] [MemberData(nameof(ComparisonTypes))] public void less_than_smaller_is_false(string left, string right) { @@ -78,16 +83,18 @@ public void less_than_smaller_is_false(string left, string right) print b; "; - string output = EvalReturningOutput(source).SingleOrDefault(); + // "False" vs "false" in interpreted and compiled mode + string output = EvalReturningOutput(source).SingleOrDefault()! + .ToLower(); - Assert.Equal("False", output); + Assert.Equal("false", output); } // // Tests for the <= (less than or equals) operator // - [Theory] + [SkippableTheory] [MemberData(nameof(ComparisonTypes))] public void less_than_or_equals_greater_is_true(string left, string right) { @@ -98,12 +105,14 @@ public void less_than_or_equals_greater_is_true(string left, string right) print b; "; - string output = EvalReturningOutput(source).SingleOrDefault(); + // "True" vs "true" in interpreted and compiled mode + string output = EvalReturningOutput(source).SingleOrDefault()! + .ToLower(); - Assert.Equal("True", output); + Assert.Equal("true", output); } - [Theory] + [SkippableTheory] [MemberData(nameof(ComparisonTypes))] public void less_than_or_equals_same_is_true(string left, string right) { @@ -114,12 +123,14 @@ public void less_than_or_equals_same_is_true(string left, string right) print b; "; - string output = EvalReturningOutput(source).SingleOrDefault(); + // "True" vs "true" in interpreted and compiled mode + string output = EvalReturningOutput(source).SingleOrDefault()! + .ToLower(); - Assert.Equal("True", output); + Assert.Equal("true", output); } - [Theory] + [SkippableTheory] [MemberData(nameof(ComparisonTypes))] public void less_than_or_equals_smaller_is_false(string left, string right) { @@ -130,16 +141,18 @@ public void less_than_or_equals_smaller_is_false(string left, string right) print b; "; - string output = EvalReturningOutput(source).SingleOrDefault(); + // "False" vs "false" in interpreted and compiled mode + string output = EvalReturningOutput(source).SingleOrDefault()! + .ToLower(); - Assert.Equal("False", output); + Assert.Equal("false", output); } // // Tests for the > (greater than) operator // - [Theory] + [SkippableTheory] [MemberData(nameof(ComparisonTypes))] public void greater_than_smaller_is_false(string left, string right) { @@ -150,12 +163,14 @@ public void greater_than_smaller_is_false(string left, string right) print b; "; - string output = EvalReturningOutput(source).SingleOrDefault(); + // "False" vs "false" in interpreted and compiled mode + string output = EvalReturningOutput(source).SingleOrDefault()! + .ToLower(); - Assert.Equal("False", output); + Assert.Equal("false", output); } - [Theory] + [SkippableTheory] [MemberData(nameof(ComparisonTypes))] public void greater_than_same_is_false(string left, string right) { @@ -166,12 +181,14 @@ public void greater_than_same_is_false(string left, string right) print b; "; - string output = EvalReturningOutput(source).SingleOrDefault(); + // "False" vs "false" in interpreted and compiled mode + string output = EvalReturningOutput(source).SingleOrDefault()! + .ToLower(); - Assert.Equal("False", output); + Assert.Equal("false", output); } - [Theory] + [SkippableTheory] [MemberData(nameof(ComparisonTypes))] public void greater_than_smaller_is_true(string left, string right) { @@ -182,15 +199,17 @@ public void greater_than_smaller_is_true(string left, string right) print b; "; - string output = EvalReturningOutput(source).SingleOrDefault(); + // "True" vs "true" in interpreted and compiled mode + string output = EvalReturningOutput(source).SingleOrDefault()! + .ToLower(); - Assert.Equal("True", output); + Assert.Equal("true", output); } // // Tests for the >= (greater than or equals) operator // - [Theory] + [SkippableTheory] [MemberData(nameof(ComparisonTypes))] public void greater_than_or_equals_larger_is_false(string left, string right) { @@ -201,12 +220,14 @@ public void greater_than_or_equals_larger_is_false(string left, string right) print b; "; - string output = EvalReturningOutput(source).SingleOrDefault(); + // "False" vs "false" in interpreted and compiled mode + string output = EvalReturningOutput(source).SingleOrDefault()! + .ToLower(); - Assert.Equal("False", output); + Assert.Equal("false", output); } - [Theory] + [SkippableTheory] [MemberData(nameof(ComparisonTypes))] public void greater_than_or_equals_same_is_true(string left, string right) { @@ -217,12 +238,14 @@ public void greater_than_or_equals_same_is_true(string left, string right) print b; "; - string output = EvalReturningOutput(source).SingleOrDefault(); + // "True" vs "true" in interpreted and compiled mode + string output = EvalReturningOutput(source).SingleOrDefault()! + .ToLower(); - Assert.Equal("True", output); + Assert.Equal("true", output); } - [Theory] + [SkippableTheory] [MemberData(nameof(ComparisonTypes))] public void greater_than_or_equals_smaller_is_true(string left, string right) { @@ -233,9 +256,11 @@ public void greater_than_or_equals_smaller_is_true(string left, string right) print b; "; - string output = EvalReturningOutput(source).SingleOrDefault(); + // "True" vs "true" in interpreted and compiled mode + string output = EvalReturningOutput(source).SingleOrDefault()! + .ToLower(); - Assert.Equal("True", output); + Assert.Equal("true", output); } // @@ -259,12 +284,14 @@ public void zero_and_negative_zero_integers_are_identical() // We deliberately do not check for equality, since even negative and positive zero floats are equal. // Instead, we check if one is smaller than the other. string source = @" - 0 < -0 + print 0 < -0; "; - object output = Eval(source); + // "False" vs "false" in interpreted and compiled mode + string output = EvalReturningOutput(source).SingleOrDefault()! + .ToLower(); - Assert.Equal(false, output); + Assert.Equal("false", output); } [Theory] @@ -274,12 +301,14 @@ public async Task zero_less_than_negative_zero_is_false(CultureInfo cultureInfo) CultureInfo.CurrentCulture = cultureInfo; string source = @" - 0.0 < -0.0 + print 0.0 < -0.0; "; - object output = Eval(source); + // "False" vs "false" in interpreted and compiled mode + string output = EvalReturningOutput(source).SingleOrDefault()! + .ToLower(); - Assert.Equal(false, output); + Assert.Equal("false", output); } [Theory] @@ -289,12 +318,14 @@ public async Task negative_zero_less_than_zero_is_false(CultureInfo cultureInfo) CultureInfo.CurrentCulture = cultureInfo; string source = @" - -0.0 < 0.0 + print -0.0 < 0.0; "; - object output = Eval(source); + // "False" vs "false" in interpreted and compiled mode + string output = EvalReturningOutput(source).SingleOrDefault()! + .ToLower(); - Assert.Equal(false, output); + Assert.Equal("false", output); } [Theory] @@ -304,12 +335,14 @@ public async Task zero_greater_than_negative_zero_is_false(CultureInfo cultureIn CultureInfo.CurrentCulture = cultureInfo; string source = @" - 0.0 > -0.0 + print 0.0 > -0.0; "; - object output = Eval(source); + // "False" vs "false" in interpreted and compiled mode + string output = EvalReturningOutput(source).SingleOrDefault()! + .ToLower(); - Assert.Equal(false, output); + Assert.Equal("false", output); } [Theory] @@ -319,12 +352,14 @@ public async Task negative_zero_greater_than_zero_is_false(CultureInfo cultureIn CultureInfo.CurrentCulture = cultureInfo; string source = @" - -0.0 > 0.0 + print -0.0 > 0.0; "; - object output = Eval(source); + // "False" vs "false" in interpreted and compiled mode + string output = EvalReturningOutput(source).SingleOrDefault()! + .ToLower(); - Assert.Equal(false, output); + Assert.Equal("false", output); } [Theory] @@ -334,12 +369,14 @@ public async Task zero_less_than_or_equals_negative_zero_is_false(CultureInfo cu CultureInfo.CurrentCulture = cultureInfo; string source = @" - 0.0 <= -0.0 + print 0.0 <= -0.0; "; - object output = Eval(source); + // "True" vs "true" in interpreted and compiled mode + string output = EvalReturningOutput(source).SingleOrDefault()! + .ToLower(); - Assert.Equal(true, output); + Assert.Equal("true", output); } [Theory] @@ -349,27 +386,31 @@ public async Task negative_zero_less_than_or_equals_zero_is_false(CultureInfo cu CultureInfo.CurrentCulture = cultureInfo; string source = @" - -0.0 <= 0.0 + print -0.0 <= 0.0; "; - object output = Eval(source); + // "True" vs "true" in interpreted and compiled mode + string output = EvalReturningOutput(source).SingleOrDefault()! + .ToLower(); - Assert.Equal(true, output); + Assert.Equal("true", output); } [Theory] [ClassData(typeof(TestCultures))] - public async Task zero_greater_than_or_equals_negative_zero_is_false(CultureInfo cultureInfo) + public async Task zero_greater_than_or_equals_negative_zero_is_true(CultureInfo cultureInfo) { CultureInfo.CurrentCulture = cultureInfo; string source = @" - 0.0 >= -0.0 + print 0.0 >= -0.0; "; - object output = Eval(source); + string output = EvalReturningOutputString(source); - Assert.Equal(true, output); + // "True" in interpreted mode and "true" in compiled mode + output.ToLower().Should() + .Be("true"); } [Theory] @@ -379,12 +420,14 @@ public async Task negative_zero_greater_than_or_equals_zero_is_false(CultureInfo CultureInfo.CurrentCulture = cultureInfo; string source = @" - -0.0 >= 0.0 + print -0.0 >= 0.0; "; - object output = Eval(source); + string output = EvalReturningOutputString(source); - Assert.Equal(true, output); + // "True" in interpreted mode and "true" in compiled mode + output.ToLower().Should() + .Be("true"); } } } diff --git a/src/Perlang.Tests.Integration/Operator/Binary/DivisionTests.cs b/src/Perlang.Tests.Integration/Operator/Binary/DivisionTests.cs index 53a893b3..d417d6a3 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/DivisionTests.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/DivisionTests.cs @@ -13,7 +13,7 @@ namespace Perlang.Tests.Integration.Operator.Binary // https://github.com/munificent/craftinginterpreters/blob/c6da0e61e6072271de404464c34b51c2fdc39e59/test/operator/divide_num_nonnum.lox public class DivisionTests { - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.Division_result), MemberType = typeof(BinaryOperatorData))] void performs_division(string i, string j, string expectedResult) { @@ -30,10 +30,12 @@ void performs_division(string i, string j, string expectedResult) .Be(expectedResult); } - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.Division_type), MemberType = typeof(BinaryOperatorData))] public void with_supported_types_returns_expected_type(string i, string j, string expectedResult) { + Skip.If(PerlangMode.ExperimentalCompilation, "Not supported in compiled mode"); + string source = $@" print ({i} / {j}).get_type(); "; @@ -59,7 +61,7 @@ public void with_unsupported_types_emits_expected_error(string i, string j, stri .Message.Should().Match(expectedResult); } - [Theory] + [SkippableTheory] [ClassData(typeof(TestCultures))] public async Task dividing_doubles_works_on_different_cultures(CultureInfo cultureInfo) { diff --git a/src/Perlang.Tests.Integration/Operator/Binary/EqualTests.cs b/src/Perlang.Tests.Integration/Operator/Binary/EqualTests.cs index 27bcda3c..af20c6d3 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/EqualTests.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/EqualTests.cs @@ -6,7 +6,7 @@ namespace Perlang.Tests.Integration.Operator.Binary; public class EqualTests { - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.Equal), MemberType = typeof(BinaryOperatorData))] void performs_equality_comparison(string i, string j, string expectedResult) { @@ -17,16 +17,17 @@ void performs_equality_comparison(string i, string j, string expectedResult) print i1 == i2; "; - string result = EvalReturningOutputString(source); + string result = EvalReturningOutputString(source) + .ToLower(); result.Should() .Be(expectedResult); } [Theory] - [InlineData("Foo", "Bar", "False")] - [InlineData("Foo", "foo", "False")] // Comparison is case sensitive - [InlineData("foo", "foo", "True")] + [InlineData("Foo", "Bar", "false")] + [InlineData("Foo", "foo", "false")] // Comparison is case sensitive + [InlineData("foo", "foo", "true")] void strings_can_be_compared_for_equality(string i, string j, string expectedResult) { string source = $@" @@ -36,7 +37,9 @@ void strings_can_be_compared_for_equality(string i, string j, string expectedRes print i1 == i2; "; - string result = EvalReturningOutputString(source); + // "True" in interpreted mode vs "true" in compiled mode + string result = EvalReturningOutputString(source) + .ToLower(); result.Should() .Be(expectedResult); diff --git a/src/Perlang.Tests.Integration/Operator/Binary/ExponentialTests.cs b/src/Perlang.Tests.Integration/Operator/Binary/ExponentialTests.cs index 2bc731e3..759c130a 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/ExponentialTests.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/ExponentialTests.cs @@ -15,7 +15,7 @@ namespace Perlang.Tests.Integration.Operator.Binary /// public class ExponentialTests { - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.Exponential_result), MemberType = typeof(BinaryOperatorData))] public void performs_exponential_calculation(string i, string j, string expectedResult) { @@ -29,7 +29,7 @@ public void performs_exponential_calculation(string i, string j, string expected .Be(expectedResult); } - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.Exponential_type), MemberType = typeof(BinaryOperatorData))] public void with_supported_types_returns_expected_type(string i, string j, string expectedResult) { @@ -78,7 +78,7 @@ public void exponential_positive_and_negative_integer_literals_throws_expected_e Assert.Equal("The number must be greater than or equal to zero. (Parameter 'exponent')", exception.Message); } - [Fact] + [SkippableFact] public void exponential_negative_and_positive_integer_literals() { string source = @" @@ -90,7 +90,7 @@ public void exponential_negative_and_positive_integer_literals() Assert.Equal(new BigInteger(-1000), result); } - [Fact] + [SkippableFact] public void exponential_negative_and_positive_integer_literals_again() { // This tests an important edge case where we differ from MRI Ruby. Try it in 'irb' and you'll see what I @@ -120,7 +120,7 @@ public void exponential_negative_and_positive_integer_literals_again() Assert.Equal(new BigInteger(59049), result); } - [Theory] + [SkippableTheory] [ClassData(typeof(TestCultures))] public async Task exponential_operator_works_on_different_cultures(CultureInfo cultureInfo) { @@ -135,7 +135,7 @@ public async Task exponential_operator_works_on_different_cultures(CultureInfo c Assert.Equal(3162.2776601683795, result); } - [Theory] + [SkippableTheory] [ClassData(typeof(TestCultures))] public async Task exponential_integer_and_float_literals_infers_to_expected_type(CultureInfo cultureInfo) { @@ -151,7 +151,7 @@ public async Task exponential_integer_and_float_literals_infers_to_expected_type Assert.Equal("System.Double", result); } - [Theory] + [SkippableTheory] [ClassData(typeof(TestCultures))] public async Task exponential_integer_and_negative_float_literals(CultureInfo cultureInfo) { @@ -166,7 +166,7 @@ public async Task exponential_integer_and_negative_float_literals(CultureInfo cu Assert.Equal(0.00031622776601683794, result); } - [Fact] + [SkippableFact] public void exponential_integer_literals_and_multiplication() { string source = @" @@ -178,7 +178,7 @@ public void exponential_integer_literals_and_multiplication() Assert.Equal(new BigInteger(2048), result); } - [Fact] + [SkippableFact] public void exponential_bigint_and_int() { string source = @" @@ -190,7 +190,7 @@ public void exponential_bigint_and_int() Assert.Equal(BigInteger.Parse("2037035976334486086268445688409378161051468393665936250636140449354381299763336706183397376"), result); } - [Fact] + [SkippableFact] public void exponential_multiple_times() { string source = @" @@ -202,7 +202,7 @@ public void exponential_multiple_times() Assert.Equal(BigInteger.Parse("2037035976334486086268445688409378161051468393665936250636140449354381299763336706183397376"), result); } - [Fact] + [SkippableFact] public void exponential_integer_literal_and_function_return_value() { string source = @" @@ -216,7 +216,7 @@ public void exponential_integer_literal_and_function_return_value() Assert.Equal("65536", result); } - [Theory] + [SkippableTheory] [InlineData("2", "12", "4096", "sv-SE")] [InlineData("2", "12", "4096", "en-US")] [InlineData("10", "3.5", "3162.2776601683795", "sv-SE")] @@ -235,7 +235,7 @@ public async Task exponential_integer_literals_as_variable_initializer(string le Assert.Equal(expectedResult, result); } - [Fact] + [SkippableFact] public void exponential_function_return_value_and_int_literal() { string source = @" @@ -249,7 +249,7 @@ public void exponential_function_return_value_and_int_literal() Assert.Equal("65536", result); } - [Fact] + [SkippableFact] public void exponential_int_variable_and_int_literal() { string source = @" @@ -263,7 +263,7 @@ public void exponential_int_variable_and_int_literal() Assert.Equal("256", result); } - [Fact] + [SkippableFact] public void exponential_function_return_values() { string source = @" @@ -278,7 +278,7 @@ public void exponential_function_return_values() Assert.Equal("256", result); } - [Fact] + [SkippableFact] public void exponential_bigint_and_negative_int_throws_expected_runtime_error() { string source = @" diff --git a/src/Perlang.Tests.Integration/Operator/Binary/GreaterEqualTests.cs b/src/Perlang.Tests.Integration/Operator/Binary/GreaterEqualTests.cs index 87ad72b4..a3fe4bbf 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/GreaterEqualTests.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/GreaterEqualTests.cs @@ -6,7 +6,7 @@ namespace Perlang.Tests.Integration.Operator.Binary; public class GreaterEqualTests { - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.GreaterEqual), MemberType = typeof(BinaryOperatorData))] void performs_greater_than_or_equal_comparison(string i, string j, string expectedResult) { @@ -17,7 +17,8 @@ void performs_greater_than_or_equal_comparison(string i, string j, string expect print i1 >= i2; "; - string result = EvalReturningOutputString(source); + string result = EvalReturningOutputString(source) + .ToLower(); result.Should() .Be(expectedResult); diff --git a/src/Perlang.Tests.Integration/Operator/Binary/GreaterTests.cs b/src/Perlang.Tests.Integration/Operator/Binary/GreaterTests.cs index 3e650afa..cfb0b415 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/GreaterTests.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/GreaterTests.cs @@ -6,7 +6,7 @@ namespace Perlang.Tests.Integration.Operator.Binary; public class GreaterTests { - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.Greater), MemberType = typeof(BinaryOperatorData))] void performs_greater_than_comparison(string i, string j, string expectedResult) { @@ -17,7 +17,8 @@ void performs_greater_than_comparison(string i, string j, string expectedResult) print i1 > i2; "; - string result = EvalReturningOutputString(source); + string result = EvalReturningOutputString(source) + .ToLower(); result.Should() .Be(expectedResult); diff --git a/src/Perlang.Tests.Integration/Operator/Binary/LessEqualTests.cs b/src/Perlang.Tests.Integration/Operator/Binary/LessEqualTests.cs index 8d83ecd3..1eed9ae5 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/LessEqualTests.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/LessEqualTests.cs @@ -6,7 +6,7 @@ namespace Perlang.Tests.Integration.Operator.Binary; public class LessEqualTests { - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.LessEqual), MemberType = typeof(BinaryOperatorData))] void performs_less_equal_comparison(string i, string j, string expectedResult) { @@ -17,7 +17,8 @@ void performs_less_equal_comparison(string i, string j, string expectedResult) print i1 <= i2; "; - string result = EvalReturningOutputString(source); + string result = EvalReturningOutputString(source) + .ToLower(); result.Should() .Be(expectedResult); diff --git a/src/Perlang.Tests.Integration/Operator/Binary/LessTests.cs b/src/Perlang.Tests.Integration/Operator/Binary/LessTests.cs index 31be0448..6c42fa37 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/LessTests.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/LessTests.cs @@ -6,7 +6,7 @@ namespace Perlang.Tests.Integration.Operator.Binary; public class LessTests { - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.Less), MemberType = typeof(BinaryOperatorData))] void performs_less_than_comparison(string i, string j, string expectedResult) { @@ -17,7 +17,8 @@ void performs_less_than_comparison(string i, string j, string expectedResult) print i1 < i2; "; - string result = EvalReturningOutputString(source); + string result = EvalReturningOutputString(source) + .ToLower(); result.Should() .Be(expectedResult); diff --git a/src/Perlang.Tests.Integration/Operator/Binary/ModuloTests.cs b/src/Perlang.Tests.Integration/Operator/Binary/ModuloTests.cs index 094d72cf..e44267e2 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/ModuloTests.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/ModuloTests.cs @@ -26,10 +26,12 @@ public void returns_remainder_of_division(string i, string j, string expectedRes .Be(expectedResult); } - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.Modulo_type), MemberType = typeof(BinaryOperatorData))] public void with_supported_types_returns_expected_type(string i, string j, string expectedResult) { + Skip.If(PerlangMode.ExperimentalCompilation, "Not supported in compiled mode"); + string source = $@" print ({i} % {j}).get_type(); "; @@ -55,7 +57,7 @@ public void with_unsupported_types_emits_expected_error(string i, string j, stri .Message.Should().Match(expectedResult); } - [Theory] + [SkippableTheory] [ClassData(typeof(TestCultures))] public async Task modulo_operation_works_on_different_cultures(CultureInfo cultureInfo) { @@ -71,7 +73,7 @@ public async Task modulo_operation_works_on_different_cultures(CultureInfo cultu Assert.Equal(0.04000000000000031, result); } - [Theory] + [SkippableTheory] [ClassData(typeof(TestCultures))] public async Task modulo_operation_combined_with_others_without_grouping(CultureInfo cultureInfo) { @@ -86,7 +88,7 @@ public async Task modulo_operation_combined_with_others_without_grouping(Culture Assert.Equal(1.9, result); } - [Theory] + [SkippableTheory] [ClassData(typeof(TestCultures))] public async Task modulo_operation_combined_with_others_with_grouping_first_operators(CultureInfo cultureInfo) { @@ -101,7 +103,7 @@ public async Task modulo_operation_combined_with_others_with_grouping_first_oper Assert.Equal(1.9, result); } - [Theory] + [SkippableTheory] [ClassData(typeof(TestCultures))] public async Task modulo_operation_combined_with_others_with_grouping_last_operators(CultureInfo cultureInfo) { diff --git a/src/Perlang.Tests.Integration/Operator/Binary/MultiplicationTests.cs b/src/Perlang.Tests.Integration/Operator/Binary/MultiplicationTests.cs index 7dfa0f12..6ee41de6 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/MultiplicationTests.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/MultiplicationTests.cs @@ -30,10 +30,12 @@ public void performs_multiplication(string i, string j, string expectedResult) .Be(expectedResult); } - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.Multiplication_type), MemberType = typeof(BinaryOperatorData))] public void with_supported_types_returns_expected_type(string i, string j, string expectedResult) { + Skip.If(PerlangMode.ExperimentalCompilation, "Not supported in compiled mode"); + string source = $@" print ({i} * {j}).get_type(); "; @@ -59,7 +61,7 @@ public void with_unsupported_types_emits_expected_error(string i, string j, stri .Message.Should().Match(expectedResult); } - [Theory] + [SkippableTheory] [ClassData(typeof(TestCultures))] public async Task multiplying_doubles_works_on_different_cultures(CultureInfo cultureInfo) { diff --git a/src/Perlang.Tests.Integration/Operator/Binary/NotEqualTests.cs b/src/Perlang.Tests.Integration/Operator/Binary/NotEqualTests.cs index 556e28fd..eb4c0e5d 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/NotEqualTests.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/NotEqualTests.cs @@ -6,7 +6,7 @@ namespace Perlang.Tests.Integration.Operator.Binary; public class NotEqualTests { - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.NotEqual), MemberType = typeof(BinaryOperatorData))] void performs_non_equality_comparison(string i, string j, string expectedResult) { @@ -19,14 +19,16 @@ void performs_non_equality_comparison(string i, string j, string expectedResult) string result = EvalReturningOutputString(source); - result.Should() + result + .ToLower() + .Should() .Be(expectedResult); } [Theory] - [InlineData("Foo", "Bar", "True")] - [InlineData("Foo", "foo", "True")] // Comparison is case sensitive - [InlineData("foo", "foo", "False")] + [InlineData("Foo", "Bar", "true")] + [InlineData("Foo", "foo", "true")] // Comparison is case sensitive + [InlineData("foo", "foo", "false")] void strings_can_be_compared_for_equality(string i, string j, string expectedResult) { string source = $@" @@ -38,7 +40,9 @@ void strings_can_be_compared_for_equality(string i, string j, string expectedRes string result = EvalReturningOutputString(source); - result.Should() + result + .ToLower() + .Should() .Be(expectedResult); } diff --git a/src/Perlang.Tests.Integration/Operator/Binary/ShiftLeftTests.cs b/src/Perlang.Tests.Integration/Operator/Binary/ShiftLeftTests.cs index 92c8a0d9..04ba8483 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/ShiftLeftTests.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/ShiftLeftTests.cs @@ -7,7 +7,7 @@ namespace Perlang.Tests.Integration.Operator.Binary; public class ShiftLeftTests { - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.ShiftLeft_result), MemberType = typeof(BinaryOperatorData))] public void performs_left_shifting(string i, string j, string expectedResult) { @@ -21,7 +21,7 @@ public void performs_left_shifting(string i, string j, string expectedResult) .Be(expectedResult); } - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.ShiftLeft_type), MemberType = typeof(BinaryOperatorData))] public void with_supported_types_returns_expected_type(string i, string j, string expectedResult) { @@ -107,7 +107,7 @@ public void takes_precedence_over_modulo_operator() Assert.Equal("24", result); } - [Fact] + [SkippableFact] public void takes_precedence_over_power_operator() { string source = @" diff --git a/src/Perlang.Tests.Integration/Operator/Binary/ShiftRightTests.cs b/src/Perlang.Tests.Integration/Operator/Binary/ShiftRightTests.cs index c9609bc7..f4c7c977 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/ShiftRightTests.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/ShiftRightTests.cs @@ -7,7 +7,7 @@ namespace Perlang.Tests.Integration.Operator.Binary; public class ShiftRightTests { - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.ShiftRight_result), MemberType = typeof(BinaryOperatorData))] public void performs_right_shifting(string i, string j, string expectedResult) { @@ -21,10 +21,12 @@ public void performs_right_shifting(string i, string j, string expectedResult) .Be(expectedResult); } - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.ShiftRight_type), MemberType = typeof(BinaryOperatorData))] public void with_supported_types_returns_expected_type(string i, string j, string expectedResult) { + Skip.If(PerlangMode.ExperimentalCompilation, "Not supported in compiled mode"); + string source = $@" print ({i} >> {j}).get_type(); "; @@ -107,7 +109,7 @@ public void takes_precedence_over_modulo_operator() Assert.Equal("24", result); } - [Fact] + [SkippableFact] public void takes_precedence_over_power_operator() { string source = @" diff --git a/src/Perlang.Tests.Integration/Operator/Binary/SubtractionAssignmentTests.cs b/src/Perlang.Tests.Integration/Operator/Binary/SubtractionAssignmentTests.cs index 76a6628f..ea4e5532 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/SubtractionAssignmentTests.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/SubtractionAssignmentTests.cs @@ -7,7 +7,7 @@ namespace Perlang.Tests.Integration.Operator.Binary; public class SubtractionAssignmentTests { - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.SubtractionAssignment_result), MemberType = typeof(BinaryOperatorData))] public void performs_subtraction_assignment(string i, string j, string expectedResult) { @@ -23,10 +23,12 @@ public void performs_subtraction_assignment(string i, string j, string expectedR .Be(expectedResult); } - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.SubtractionAssignment_type), MemberType = typeof(BinaryOperatorData))] void with_supported_types_returns_expected_type(string i, string j, string expectedType) { + Skip.If(PerlangMode.ExperimentalCompilation, "Not supported in compiled mode"); + string source = $@" var i = {i}; diff --git a/src/Perlang.Tests.Integration/Operator/Binary/SubtractionTests.cs b/src/Perlang.Tests.Integration/Operator/Binary/SubtractionTests.cs index 1054dd7c..6cb079f9 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/SubtractionTests.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/SubtractionTests.cs @@ -6,7 +6,7 @@ namespace Perlang.Tests.Integration.Operator.Binary; public class SubtractionTests { - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.Subtraction_result), MemberType = typeof(BinaryOperatorData))] void performs_subtraction(string i, string j, string expectedResult) { @@ -23,10 +23,12 @@ void performs_subtraction(string i, string j, string expectedResult) .Be(expectedResult); } - [Theory] + [SkippableTheory] [MemberData(nameof(BinaryOperatorData.Subtraction_type), MemberType = typeof(BinaryOperatorData))] void with_supported_types_returns_expected_type(string i, string j, string expectedType) { + Skip.If(PerlangMode.ExperimentalCompilation, "Not supported in compiled mode"); + string source = $@" var i1 = {i}; var i2 = {j}; diff --git a/src/Perlang.Tests.Integration/Operator/PostfixDecrement.cs b/src/Perlang.Tests.Integration/Operator/PostfixDecrement.cs index 75d67e72..8bf7dcc2 100644 --- a/src/Perlang.Tests.Integration/Operator/PostfixDecrement.cs +++ b/src/Perlang.Tests.Integration/Operator/PostfixDecrement.cs @@ -29,7 +29,7 @@ public async Task decrementing_defined_variable(CultureInfo cultureInfo) Assert.Equal(new[] { "-1" }, output); } - [Theory] + [SkippableTheory] [InlineData("int", "1", "System.Int32", "en-US")] [InlineData("int", "1", "System.Int32", "sv-SE")] [InlineData("long", "4294967296", "System.Int64", "en-US")] diff --git a/src/Perlang.Tests.Integration/Operator/PostfixIncrement.cs b/src/Perlang.Tests.Integration/Operator/PostfixIncrement.cs index b20b78be..3609054f 100644 --- a/src/Perlang.Tests.Integration/Operator/PostfixIncrement.cs +++ b/src/Perlang.Tests.Integration/Operator/PostfixIncrement.cs @@ -12,7 +12,7 @@ public class PostfixIncrement // "Positive" tests, testing for supported behavior // - [Theory] + [SkippableTheory] [InlineData("int", "0", "1", "en-US")] [InlineData("int", "0", "1", "sv-SE")] [InlineData("long", "4294967296", "4294967297", "en-US")] @@ -36,7 +36,7 @@ public async Task incrementing_variable_assigns_expected_value(string type, stri Assert.Equal(after, output); } - [Theory] + [SkippableTheory] [InlineData("int", "0", "System.Int32", "en-US")] [InlineData("int", "0", "System.Int32", "sv-SE")] [InlineData("long", "4294967296", "System.Int64", "en-US")] diff --git a/src/Perlang.Tests.Integration/Operator/PrefixNegation.cs b/src/Perlang.Tests.Integration/Operator/PrefixNegation.cs index 5ccb1d29..7a9513a5 100644 --- a/src/Perlang.Tests.Integration/Operator/PrefixNegation.cs +++ b/src/Perlang.Tests.Integration/Operator/PrefixNegation.cs @@ -10,6 +10,21 @@ namespace Perlang.Tests.Integration.Operator; public class PrefixNegation { [Fact] + public void negation_of_negative_int_print() + { + // This test is a bit weaker than the test which tests both the value and the type, but the latter is currently + // not possible to achieve in compiled mode. + string source = @" + print(- -100); + "; + + string result = EvalReturningOutputString(source); + + result.Should() + .Be("100"); + } + + [SkippableFact] public void negation_of_negative_int() { string source = @" @@ -23,6 +38,21 @@ public void negation_of_negative_int() } [Fact] + public void negation_of_positive_int_print() + { + // Note: there is a certain logic in the parsing of the unary prefix operator, converting "- 123" into a single + // literal expression. To ensure that this logic is circumvented, we add grouping parentheses. + string source = @" + print -(123); + "; + + string result = EvalReturningOutputString(source); + + result.Should() + .Be("-123"); + } + + [SkippableFact] public void negation_of_positive_int() { // Note: there is a certain logic in the parsing of the unary prefix operator, converting "- 123" into a single diff --git a/src/Perlang.Tests.Integration/Perlang.Tests.Integration.csproj b/src/Perlang.Tests.Integration/Perlang.Tests.Integration.csproj index 875400d2..27590227 100644 --- a/src/Perlang.Tests.Integration/Perlang.Tests.Integration.csproj +++ b/src/Perlang.Tests.Integration/Perlang.Tests.Integration.csproj @@ -19,6 +19,7 @@ + diff --git a/src/Perlang.Tests.Integration/Precedence.cs b/src/Perlang.Tests.Integration/Precedence.cs index d6a25ff7..e26684e4 100644 --- a/src/Perlang.Tests.Integration/Precedence.cs +++ b/src/Perlang.Tests.Integration/Precedence.cs @@ -6,55 +6,55 @@ namespace Perlang.Tests.Integration // Based on https://github.com/munificent/craftinginterpreters/blob/master/test/precedence.lox public class Precedence { - [Fact] + [SkippableFact] public void multiply_has_higher_precedence_than_plus() { Assert.Equal(14, Eval("2 + 3 * 4")); } - [Fact] + [SkippableFact] public void multiply_has_higher_precedence_than_minus() { Assert.Equal(8, Eval("20 - 3 * 4")); } - [Fact] + [SkippableFact] public void divide_has_higher_precedence_than_plus() { Assert.Equal(4, Eval("2 + 6 / 3")); } - [Fact] + [SkippableFact] public void divide_has_higher_precedence_than_minus() { Assert.Equal(0, Eval("2 - 6 / 3")); } - [Fact] + [SkippableFact] public void less_than_has_higher_precedence_than_equals_equals() { Assert.Equal(true, Eval("false == 2 < 1")); } - [Fact] + [SkippableFact] public void greater_than_has_higher_precedence_than_equals_equals() { Assert.Equal(true, Eval("false == 1 > 2")); } - [Fact] + [SkippableFact] public void less_than_or_equals_has_higher_precedence_than_equals_equals() { Assert.Equal(true, Eval("false == 2 <= 1")); } - [Fact] + [SkippableFact] public void greater_than_or_equals_has_higher_precedence_than_equals_equals() { Assert.Equal(true, Eval("false == 1 >= 2")); } - [Fact] + [SkippableFact] public void one_minus_one_is_not_space_sensitive() { Assert.Equal(0, Eval("1 - 1")); @@ -63,7 +63,7 @@ public void one_minus_one_is_not_space_sensitive() Assert.Equal(0, Eval("1-1")); } - [Fact] + [SkippableFact] public void parentheses_can_be_used_for_grouping() { Assert.Equal(4, Eval("(2 * (6 - (2 + 2)))")); diff --git a/src/Perlang.Tests.Integration/Return.cs b/src/Perlang.Tests.Integration/Return.cs index 30faf3af..fcab7741 100644 --- a/src/Perlang.Tests.Integration/Return.cs +++ b/src/Perlang.Tests.Integration/Return.cs @@ -109,11 +109,13 @@ class Foo { // TODO: This is an oversight; as of https://github.com/perlang-org/perlang/pull/54, these semantics should no // longer be supported. Automatically returning `null` when no value is provided is wrong. This should only be // supported when the return type is explicitly declared as `void`. - [Fact] + [SkippableFact] public void return_null_if_no_value() { + Skip.If(PerlangMode.ExperimentalCompilation, "Not supported in compiled mode"); + string source = @" - fun f(): string { + fun f(): void { return; print ""bad""; } diff --git a/src/Perlang.Tests.Integration/Stdlib/ArgvTests.cs b/src/Perlang.Tests.Integration/Stdlib/ArgvTests.cs index 00e7267d..ae57136e 100644 --- a/src/Perlang.Tests.Integration/Stdlib/ArgvTests.cs +++ b/src/Perlang.Tests.Integration/Stdlib/ArgvTests.cs @@ -8,13 +8,13 @@ namespace Perlang.Tests.Integration.Stdlib { public class ArgvTests { - [Fact] + [SkippableFact] public void ARGV_is_defined() { Assert.IsAssignableFrom(Eval("ARGV")); } - [Fact] + [SkippableFact] public void ARGV_pop_is_defined() { Assert.IsAssignableFrom(Eval("ARGV.pop")); @@ -30,13 +30,13 @@ public void ARGV_pop_with_no_arguments_throws_the_expected_exception() Assert.Matches("No arguments left", exception.Message); } - [Fact] + [SkippableFact] public void ARGV_pop_with_one_argument_expr_returns_the_expected_result() { Assert.Equal("arg1", EvalWithArguments("ARGV.pop()", "arg1")); } - [Fact] + [SkippableFact] public void ARGV_pop_with_one_argument_stmt_returns_the_expected_result() { var result = EvalReturningOutput("print ARGV.pop();", "arg1").SingleOrDefault(); @@ -44,7 +44,7 @@ public void ARGV_pop_with_one_argument_stmt_returns_the_expected_result() Assert.Equal("arg1", result); } - [Fact] + [SkippableFact] public void ARGV_pop_with_multiple_arguments_returns_the_expected_result() { var result = EvalReturningOutput("ARGV.pop(); print ARGV.pop();", "arg1", "arg2").Single(); diff --git a/src/Perlang.Tests.Integration/Stdlib/Base64DecodeTests.cs b/src/Perlang.Tests.Integration/Stdlib/Base64DecodeTests.cs index 975e9a4a..c0232dab 100644 --- a/src/Perlang.Tests.Integration/Stdlib/Base64DecodeTests.cs +++ b/src/Perlang.Tests.Integration/Stdlib/Base64DecodeTests.cs @@ -7,7 +7,7 @@ namespace Perlang.Tests.Integration.Stdlib { public class Base64DecodeTests { - [Fact] + [SkippableFact] public void Base64_decode_is_defined() { Assert.IsAssignableFrom(Eval("Base64.decode")); @@ -23,13 +23,13 @@ public void Base64_decode_with_no_arguments_throws_the_expected_exception() Assert.Contains("Method 'decode' has 1 parameter(s) but was called with 0 argument(s)", exception.Message); } - [Fact] + [SkippableFact] public void Base64_decode_with_a_padded_string_argument_returns_the_expected_result() { Assert.Equal("hej hej", Eval("Base64.decode(\"aGVqIGhlag==\")")); } - [Fact] + [SkippableFact] public void Base64_decode_with_an_non_padded_string_argument_returns_the_expected_result() { // This used to fail at one point, which is why we added a test for it. diff --git a/src/Perlang.Tests.Integration/Stdlib/Base64EncodeTests.cs b/src/Perlang.Tests.Integration/Stdlib/Base64EncodeTests.cs index 5807ee4d..d4b2894e 100644 --- a/src/Perlang.Tests.Integration/Stdlib/Base64EncodeTests.cs +++ b/src/Perlang.Tests.Integration/Stdlib/Base64EncodeTests.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Text; +using FluentAssertions; using Xunit; using static Perlang.Tests.Integration.EvalHelper; @@ -17,13 +18,20 @@ public void Base64_encode_with_no_arguments_throws_the_expected_exception() Assert.Contains("Method 'encode' has 1 parameter(s) but was called with 0 argument(s)", exception.Message); } - [Fact] + [SkippableFact] public void Base64_encode_with_a_string_argument_returns_the_expected_result() { - Assert.Equal("aGVqIGhlag==", Eval("Base64.encode(\"hej hej\")")); + string source = @" + print Base64.encode(""hej hej""); + "; + + string output = EvalReturningOutputString(source); + + output.Should() + .Be("aGVqIGhlag=="); } - [Fact] + [SkippableFact] public void Base64_encode_with_a_long_string_argument_returns_the_expected_result() { var sb = new StringBuilder(); @@ -33,13 +41,18 @@ public void Base64_encode_with_a_long_string_argument_returns_the_expected_resul sb.Append("hej hej, hemskt mycket hej"); } + string source = $@" + print Base64.encode(""{sb}""); + "; + // At the moment, all lines are wrapped at every 76 characters. We could consider to make this configurable, // but it's awkward until we support method overloading. - Assert.Equal( - "aGVqIGhlaiwgaGVtc2t0IG15Y2tldCBoZWpoZWogaGVqLCBoZW1za3QgbXlja2V0IGhlamhlaiBo\r\n" + - "ZWosIGhlbXNrdCBteWNrZXQgaGVqaGVqIGhlaiwgaGVtc2t0IG15Y2tldCBoZWou", - Eval($"Base64.encode(\"{sb}.\")") - ); + string output = EvalReturningOutputString(source); + + output.Should() + .Be("aGVqIGhlaiwgaGVtc2t0IG15Y2tldCBoZWpoZWogaGVqLCBoZW1za3QgbXlja2V0IGhlamhlaiBo\r\n" + + "ZWosIGhlbXNrdCBteWNrZXQgaGVqaGVqIGhlaiwgaGVtc2t0IG15Y2tldCBoZWo=" + ); } [Fact] diff --git a/src/Perlang.Tests.Integration/Stdlib/LibcTests.cs b/src/Perlang.Tests.Integration/Stdlib/LibcTests.cs index fe1e0142..3ed22833 100644 --- a/src/Perlang.Tests.Integration/Stdlib/LibcTests.cs +++ b/src/Perlang.Tests.Integration/Stdlib/LibcTests.cs @@ -10,7 +10,7 @@ namespace Perlang.Tests.Integration.Stdlib /// public class LibcTests { - [Fact] + [SkippableFact] public void environ_returns_dictionary() { var result = Eval("Libc.environ()"); @@ -20,7 +20,7 @@ public void environ_returns_dictionary() .NotBeEmpty(); } - [Fact] + [SkippableFact] public void environ_contains_path() { var result = Eval("Libc.environ()"); @@ -35,7 +35,7 @@ public void environ_contains_path() #if _WINDOWS [Fact(Skip = "PATH is named Path on Windows")] #else - [Fact] + [SkippableFact] #endif public void environ_item_supports_get_Item() { @@ -47,7 +47,7 @@ public void environ_item_supports_get_Item() .NotBeEmpty(); } - [Fact] + [SkippableFact] public void getcwd_returns_non_null_string() { var result = Eval("Libc.getcwd()"); @@ -57,7 +57,7 @@ public void getcwd_returns_non_null_string() .NotBeNull(); } - [Fact] + [SkippableFact] public void getenv_path_returns_non_empty_string() { var result = Eval("Libc.getenv(\"PATH\")"); @@ -67,7 +67,7 @@ public void getenv_path_returns_non_empty_string() .NotBeEmpty(); } - [Fact] + [SkippableFact] public void getpid_returns_positive_integer() { var result = Eval("Libc.getpid()"); diff --git a/src/Perlang.Tests.Integration/Stdlib/PosixTests.cs b/src/Perlang.Tests.Integration/Stdlib/PosixTests.cs index 89dbea94..547836ff 100644 --- a/src/Perlang.Tests.Integration/Stdlib/PosixTests.cs +++ b/src/Perlang.Tests.Integration/Stdlib/PosixTests.cs @@ -12,7 +12,7 @@ public class PosixTests #if _WINDOWS [Fact(Skip = "Only supported on POSIX platforms")] #else - [Fact] + [SkippableFact] #endif public void getegid_returns_positive_integer() { @@ -26,7 +26,7 @@ public void getegid_returns_positive_integer() #if _WINDOWS [Fact(Skip = "Only supported on POSIX platforms")] #else - [Fact] + [SkippableFact] #endif public void geteuid_returns_positive_integer() { @@ -40,7 +40,7 @@ public void geteuid_returns_positive_integer() #if _WINDOWS [Fact(Skip = "Only supported on POSIX platforms")] #else - [Fact] + [SkippableFact] #endif public void getgid_returns_positive_integer() { @@ -54,7 +54,7 @@ public void getgid_returns_positive_integer() #if _WINDOWS [Fact(Skip = "Only supported on POSIX platforms")] #else - [Fact] + [SkippableFact] #endif public void getppid_returns_positive_integer() { @@ -68,7 +68,7 @@ public void getppid_returns_positive_integer() #if _WINDOWS [Fact(Skip = "Only supported on POSIX platforms")] #else - [Fact] + [SkippableFact] #endif public void getuid_returns_positive_integer() { diff --git a/src/Perlang.Tests.Integration/Stdlib/TimeTests.cs b/src/Perlang.Tests.Integration/Stdlib/TimeTests.cs index 27b1c6f0..e69ed192 100644 --- a/src/Perlang.Tests.Integration/Stdlib/TimeTests.cs +++ b/src/Perlang.Tests.Integration/Stdlib/TimeTests.cs @@ -1,4 +1,5 @@ using System; +using FluentAssertions; using Perlang.Interpreter; using Xunit; using static Perlang.Tests.Integration.EvalHelper; @@ -7,13 +8,13 @@ namespace Perlang.Tests.Integration.Stdlib { public class TimeTests { - [Fact] + [SkippableFact] public void Time_now_is_defined() { Assert.IsAssignableFrom(Eval("Time.now")); } - [Fact] + [SkippableFact] public void Time_now_returns_a_DateTime_value() { Assert.IsType(Eval("Time.now()")); @@ -24,13 +25,29 @@ public void Time_now_returns_a_DateTime_value() [Fact] public void Time_now_get_Ticks_returns_a_value_greater_than_zero() { - Assert.True((long) Eval("Time.now().get_Ticks()") > 0); + string source = @" + print Time.now().get_Ticks() > 0; + "; + + string output = EvalReturningOutputString(source); + + // "True" in interpreted mode and "true" in compiled mode + output.ToLower().Should() + .Be("true"); } [Fact] public void Time_now_ticks_returns_a_value_greater_than_zero() { - Assert.True((long) Eval("Time.now().ticks()") > 0); + string source = @" + print Time.now().ticks() > 0; + "; + + string output = EvalReturningOutputString(source); + + // "True" in interpreted mode and "true" in compiled mode + output.ToLower().Should() + .Be("true"); } } } diff --git a/src/Perlang.Tests.Integration/Typing/BigintTests.cs b/src/Perlang.Tests.Integration/Typing/BigintTests.cs index 17961682..4cf4a085 100644 --- a/src/Perlang.Tests.Integration/Typing/BigintTests.cs +++ b/src/Perlang.Tests.Integration/Typing/BigintTests.cs @@ -9,7 +9,7 @@ namespace Perlang.Tests.Integration.Typing /// public class BigintTests { - [Fact] + [SkippableFact] public void bigint_variable_can_be_printed() { string source = @" @@ -23,7 +23,7 @@ public void bigint_variable_can_be_printed() Assert.Equal("103", output); } - [Fact] + [SkippableFact] public void bigint_variable_can_be_reassigned() { string source = @" @@ -38,7 +38,7 @@ public void bigint_variable_can_be_reassigned() Assert.Equal("8589934592", result); } - [Fact] + [SkippableFact] public void bigint_variable_can_contain_numbers_larger_than_32_bits() { string source = @" @@ -52,7 +52,7 @@ public void bigint_variable_can_contain_numbers_larger_than_32_bits() Assert.Equal("8589934592", output); } - [Fact] + [SkippableFact] public void bigint_variable_can_contain_numbers_larger_than_64_bits() { string source = @" @@ -66,7 +66,7 @@ public void bigint_variable_can_contain_numbers_larger_than_64_bits() Assert.Equal("1231231230912839019312831232", output); } - [Fact] + [SkippableFact] public void bigint_variable_has_expected_type_when_initialized_to_8bit_value() { // An 8-bit integer (sbyte) should be expanded to bigint when the assignment target is of the 'bigint' type. @@ -81,7 +81,7 @@ public void bigint_variable_has_expected_type_when_initialized_to_8bit_value() Assert.Equal("System.Numerics.BigInteger", output); } - [Fact] + [SkippableFact] public void bigint_variable_has_expected_type_when_assigned_8bit_value_from_another_variable() { // An 8-bit integer (sbyte) should be expanded to bigint when the assignment target is of the 'bigint' type. @@ -97,7 +97,7 @@ public void bigint_variable_has_expected_type_when_assigned_8bit_value_from_anot Assert.Equal("System.Numerics.BigInteger", output); } - [Fact] + [SkippableFact] public void bigint_variable_has_expected_type_for_large_value() { string source = @" diff --git a/src/Perlang.Tests.Integration/Typing/DoubleTests.cs b/src/Perlang.Tests.Integration/Typing/DoubleTests.cs index 8684a415..66482f9e 100644 --- a/src/Perlang.Tests.Integration/Typing/DoubleTests.cs +++ b/src/Perlang.Tests.Integration/Typing/DoubleTests.cs @@ -86,7 +86,7 @@ public void double_variable_throws_expected_exception_when_assigned_to_long_vari Assert.Matches("Cannot assign double to long variable", exception.Message); } - [Fact] + [SkippableFact] public void double_variable_has_expected_type_when_initialized_to_int_value() { // A 32-bit integer should be converted to `double` when assigned to a variable of that type. @@ -101,7 +101,7 @@ public void double_variable_has_expected_type_when_initialized_to_int_value() Assert.Equal("System.Double", output); } - [Fact] + [SkippableFact] public void double_variable_has_expected_type_when_assigned_int_value_from_another_variable() { // A 32-bit integer should be converted to `double` when assigned to a variable of that type. @@ -117,7 +117,7 @@ public void double_variable_has_expected_type_when_assigned_int_value_from_anoth Assert.Equal("System.Double", output); } - [Fact] + [SkippableFact] public void double_variable_has_expected_type_for_large_value() { string source = @" diff --git a/src/Perlang.Tests.Integration/Typing/FloatTests.cs b/src/Perlang.Tests.Integration/Typing/FloatTests.cs index cee6f32c..30f4f36b 100644 --- a/src/Perlang.Tests.Integration/Typing/FloatTests.cs +++ b/src/Perlang.Tests.Integration/Typing/FloatTests.cs @@ -101,7 +101,7 @@ public void float_variable_throws_expected_exception_when_assigned_to_long_varia .Message.Should().Match("Cannot assign float to long variable"); } - [Fact] + [SkippableFact] public void float_variable_has_expected_type_when_initialized_to_int_value() { // A 32-bit integer should be converted to `float` when assigned to a variable of that type. @@ -116,7 +116,7 @@ public void float_variable_has_expected_type_when_initialized_to_int_value() Assert.Equal("System.Single", output); } - [Fact] + [SkippableFact] public void float_variable_has_expected_type_when_assigned_int_value_from_another_variable() { // A 32-bit integer should be converted to `float` when assigned to a variable of that type. @@ -132,7 +132,7 @@ public void float_variable_has_expected_type_when_assigned_int_value_from_anothe Assert.Equal("System.Single", output); } - [Fact] + [SkippableFact] public void float_variable_has_expected_type_for_large_value() { string source = @" diff --git a/src/Perlang.Tests.Integration/Typing/IntTests.cs b/src/Perlang.Tests.Integration/Typing/IntTests.cs index 26606ca3..555563e1 100644 --- a/src/Perlang.Tests.Integration/Typing/IntTests.cs +++ b/src/Perlang.Tests.Integration/Typing/IntTests.cs @@ -9,7 +9,7 @@ namespace Perlang.Tests.Integration.Typing; /// public class IntTests { - [Fact] + [SkippableFact] public void int_variable_has_expected_type_when_initialized_to_8bit_value() { // An 8-bit integer (sbyte) should be expanded to 32-bit when the assignment target is of the 'int' type. @@ -25,7 +25,7 @@ public void int_variable_has_expected_type_when_initialized_to_8bit_value() .Be("System.Int32"); } - [Fact] + [SkippableFact] public void int_variable_has_expected_type_when_initialized_from_int_variable_with_negative_value() { string source = @" diff --git a/src/Perlang.Tests.Integration/Typing/LongTests.cs b/src/Perlang.Tests.Integration/Typing/LongTests.cs index 8e2b73fa..d3fd16e6 100644 --- a/src/Perlang.Tests.Integration/Typing/LongTests.cs +++ b/src/Perlang.Tests.Integration/Typing/LongTests.cs @@ -68,7 +68,7 @@ public void long_variable_throws_expected_exception_on_constant_overflow() Assert.Matches("Cannot assign bigint to long variable", exception.Message); } - [Fact] + [SkippableFact] public void long_variable_has_expected_type_when_initialized_to_8bit_value() { // An 8-bit integer (sbyte) should be expanded to 64-bit when the assignment target is of the 'long' type. @@ -83,7 +83,7 @@ public void long_variable_has_expected_type_when_initialized_to_8bit_value() Assert.Equal("System.Int64", output); } - [Fact] + [SkippableFact] public void long_variable_has_expected_type_when_assigned_8bit_value_from_another_variable() { // An 8-bit integer (sbyte) should be expanded to 64-bit when the assignment target is of the 'long' type. @@ -99,7 +99,7 @@ public void long_variable_has_expected_type_when_assigned_8bit_value_from_anothe Assert.Equal("System.Int64", output); } - [Fact] + [SkippableFact] public void long_variable_has_expected_type_for_large_value() { string source = @" diff --git a/src/Perlang.Tests.Integration/Typing/StringTests.cs b/src/Perlang.Tests.Integration/Typing/StringTests.cs index 5e7f3f53..3910656f 100644 --- a/src/Perlang.Tests.Integration/Typing/StringTests.cs +++ b/src/Perlang.Tests.Integration/Typing/StringTests.cs @@ -54,7 +54,7 @@ public void ascii_string_inferred_variable_can_be_reassigned_with_non_ascii_valu .Be("this is a string with non-ASCII characters: åäöÅÄÖéèüÜÿŸïÏ"); } - [Fact] + [SkippableFact] public void ascii_string_variable_has_expected_type() { string source = @" diff --git a/src/Perlang.Tests.Integration/Typing/TypingTests.cs b/src/Perlang.Tests.Integration/Typing/TypingTests.cs index 50e2cc31..50ca8b7d 100644 --- a/src/Perlang.Tests.Integration/Typing/TypingTests.cs +++ b/src/Perlang.Tests.Integration/Typing/TypingTests.cs @@ -107,7 +107,7 @@ fun bar(): void { // These tests deliberately test the edge cases (minimum/maximum values), to prevent off-by-one errors. - [Theory] + [SkippableTheory] [InlineData("2147483647", "System.Int32")] // Int32.MaxValue [InlineData("-2147483648", "System.Int32")] // Int32.MinValue [InlineData("4294967295", "System.UInt32")] // UInt32.MaxValue @@ -129,7 +129,7 @@ public void var_declaration_infers_type_from_initializer(string value, string ex Assert.Equal(expectedType, result); } - [Fact] + [SkippableFact] public void var_declaration_with_initializer_correctly_infers_type_from_assignment_source_int_variable() { string source = @" @@ -143,7 +143,7 @@ public void var_declaration_with_initializer_correctly_infers_type_from_assignme Assert.Equal("System.Int32", result); } - [Fact] + [SkippableFact] public void var_declaration_with_initializer_correctly_infers_type_from_assignment_source_long_variable() { string source = @" @@ -157,7 +157,7 @@ public void var_declaration_with_initializer_correctly_infers_type_from_assignme Assert.Equal("System.Int64", result); } - [Fact] + [SkippableFact] public void var_declaration_with_initializer_correctly_infers_type_from_assignment_source_expression() { string source = @" diff --git a/src/Perlang.Tests.Integration/Typing/UintTests.cs b/src/Perlang.Tests.Integration/Typing/UintTests.cs index a18c3179..ec32cfd1 100644 --- a/src/Perlang.Tests.Integration/Typing/UintTests.cs +++ b/src/Perlang.Tests.Integration/Typing/UintTests.cs @@ -28,7 +28,7 @@ public void uint_variable_can_be_printed(string value, string expectedOutput) Assert.Equal(expectedOutput, output); } - [Theory] + [SkippableTheory] [InlineData("103", "System.UInt32")] [InlineData("2147483647", "System.UInt32")] // Int32.MaxValue [InlineData("2147483648", "System.UInt32")] // Int32.MaxValue + 1 @@ -77,7 +77,7 @@ public void uint_variable_throws_expected_exception_on_constant_overflow() Assert.Matches("Cannot assign bigint to uint variable", exception.Message); } - [Fact] + [SkippableFact] public void uint_variable_has_expected_type_when_initialized_to_8bit_value() { // An 8-bit integer (sbyte) should be expanded to 32-bit when the assignment target is of the `uint` type. @@ -92,7 +92,7 @@ public void uint_variable_has_expected_type_when_initialized_to_8bit_value() Assert.Equal("System.UInt32", output); } - [Fact] + [SkippableFact] public void uint_variable_has_expected_type_when_assigned_8bit_value_from_another_variable() { // An 8-bit integer (sbyte) should be expanded to 32-bit when the assignment target is of the `uint` type. diff --git a/src/Perlang.Tests/Interpreter/Resolution/NameResolverTest.cs b/src/Perlang.Tests/Interpreter/Resolution/NameResolverTest.cs index 2da94f19..6c042c46 100644 --- a/src/Perlang.Tests/Interpreter/Resolution/NameResolverTest.cs +++ b/src/Perlang.Tests/Interpreter/Resolution/NameResolverTest.cs @@ -31,7 +31,7 @@ private static (Stmt Stmt, NameResolver Resolver) ScanParseAndResolveSingleState { var interpreter = new PerlangInterpreter(AssertFailRuntimeErrorHandler, s => throw new ApplicationException(s.ToString())); - var scanAndParseResult = interpreter.ScanAndParse( + var scanAndParseResult = PerlangParser.ScanAndParse( program, AssertFailScanErrorHandler, AssertFailParseErrorHandler diff --git a/src/Perlang.Tests/Interpreter/Typing/TypeResolverTest.cs b/src/Perlang.Tests/Interpreter/Typing/TypeResolverTest.cs index 2a54aa3d..fd347ed1 100644 --- a/src/Perlang.Tests/Interpreter/Typing/TypeResolverTest.cs +++ b/src/Perlang.Tests/Interpreter/Typing/TypeResolverTest.cs @@ -122,7 +122,7 @@ private static (List Stmt, NameResolver Resolver) ScanParseResolveAndTypeR { var interpreter = new PerlangInterpreter(AssertFailRuntimeErrorHandler, s => throw new ApplicationException(s.ToString())); - var scanAndParseResult = interpreter.ScanAndParse( + var scanAndParseResult = PerlangParser.ScanAndParse( program, AssertFailScanErrorHandler, AssertFailParseErrorHandler