diff --git a/.github/workflows/blank.yml b/.github/workflows/blank.yml index c8284f8..47fac2e 100644 --- a/.github/workflows/blank.yml +++ b/.github/workflows/blank.yml @@ -4,16 +4,16 @@ on: [push, pull_request] jobs: build: - runs-on: windows-2019 + runs-on: windows-2025 steps: - uses: actions/checkout@v1 - name: Setup .NetCore uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.100 + dotnet-version: 10.0.100 - name: Build with dotnet run: | - dotnet build --configuration Release + dotnet build -c Release - name: Unit Tests run: | - dotnet test + dotnet test -c Release diff --git a/README.md b/README.md index 1c7899a..4d13578 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ operating XTMF2. ### Requirements -1. DotNet Core 3.0+ SDK +1. DotNet Core 10.0+ SDK ### Clone the XTMF2 repository @@ -28,9 +28,9 @@ operating XTMF2. ### Compile from command line -> dotnet build +> dotnet build -c Release -> dotnet test +> dotnet test -c Release ## Main Branches diff --git a/XTMF2.sln b/XTMF2.sln index 140646f..86df3f6 100644 --- a/XTMF2.sln +++ b/XTMF2.sln @@ -10,7 +10,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionFiles", "SolutionFi .github\workflows\blank.yml = .github\workflows\blank.yml ..\LICENSE = ..\LICENSE README.md = README.md - XTMF2.vsdx = XTMF2.vsdx EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9040CC77-712E-414D-8453-391F9F348218}" @@ -19,7 +18,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XTMF2", "src\XTMF2\XTMF2.cs EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XTMF2.Interfaces", "src\XTMF2.Interfaces\XTMF2.Interfaces.csproj", "{5E0E170B-5A98-41C0-A9AB-4A0ED28C5CF1}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XTMF2.Client", "src\XTMF2.Client\XTMF2.Client.csproj", "{A4FC5ADC-58CA-429A-9626-EF07907AFB28}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XTMF2.RunServer", "src\XTMF2.Client\XTMF2.RunServer.csproj", "{A4FC5ADC-58CA-429A-9626-EF07907AFB28}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{77789778-93F5-46A6-9114-CCE855E5919C}" EndProject diff --git a/XTMF2.vsdx b/XTMF2.vsdx deleted file mode 100644 index 730677a..0000000 Binary files a/XTMF2.vsdx and /dev/null differ diff --git a/src/XTMF2.Client/Program.cs b/src/XTMF2.Client/Program.cs index dbc2b6c..8472a2d 100644 --- a/src/XTMF2.Client/Program.cs +++ b/src/XTMF2.Client/Program.cs @@ -100,7 +100,7 @@ private static void RunClient(Stream serverStream, List extraDlls, Syste { loadedConfig.LoadAssembly(dll); } - using var clientBus = new ClientBus(serverStream, true, runtime, extraDlls); + using var clientBus = new RunServerBus(serverStream, true, runtime, extraDlls); clientBus.ProcessRequests(); } } diff --git a/src/XTMF2.Client/XTMF2.Client.csproj b/src/XTMF2.Client/XTMF2.RunServer.csproj similarity index 85% rename from src/XTMF2.Client/XTMF2.Client.csproj rename to src/XTMF2.Client/XTMF2.RunServer.csproj index 427b2a8..fe96c57 100644 --- a/src/XTMF2.Client/XTMF2.Client.csproj +++ b/src/XTMF2.Client/XTMF2.RunServer.csproj @@ -2,8 +2,8 @@ Exe - net6.0 - XTMF2.Client + net10.0 + XTMF2.RunServer exe @@ -11,7 +11,6 @@ - PreserveNewest diff --git a/src/XTMF2.Interfaces/XTMF2.Interfaces.csproj b/src/XTMF2.Interfaces/XTMF2.Interfaces.csproj index fbd674c..6bd0260 100644 --- a/src/XTMF2.Interfaces/XTMF2.Interfaces.csproj +++ b/src/XTMF2.Interfaces/XTMF2.Interfaces.csproj @@ -1,7 +1,7 @@  - net6.0 + net10.0 XTMF2 enable diff --git a/src/XTMF2.Run/XTMF2.Run.csproj b/src/XTMF2.Run/XTMF2.Run.csproj index 44f234a..ca3d18a 100644 --- a/src/XTMF2.Run/XTMF2.Run.csproj +++ b/src/XTMF2.Run/XTMF2.Run.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net10.0 enable diff --git a/src/XTMF2/ArbitraryParameterParser.cs b/src/XTMF2/ArbitraryParameterParser.cs index ce992e9..e062ca4 100644 --- a/src/XTMF2/ArbitraryParameterParser.cs +++ b/src/XTMF2/ArbitraryParameterParser.cs @@ -19,6 +19,7 @@ You should have received a copy of the GNU General Public License using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace XTMF2 @@ -109,9 +110,9 @@ public static (bool Sucess, object? Value) ArbitraryParameterParse(Type type, st /// The value held as a string /// Contains an error if this returns false /// True if it is a value, false otherwise with a reason inside of error. - public static bool Check(Type type, string value, ref string? error) + public static bool Check(Type type, string value, [NotNullWhen(false)] ref string? error) { - return ArbitraryParameterParse(type, value, ref error).Sucess; + return ArbitraryParameterParse(type, value, ref error).Sucess; } private static (bool, object?) ErrorTryParse(string input, ref string? error, MethodInfo errorTryParse) diff --git a/src/XTMF2/Bus/RunContext.cs b/src/XTMF2/Bus/RunContext.cs index afd02d4..2ef3ace 100644 --- a/src/XTMF2/Bus/RunContext.cs +++ b/src/XTMF2/Bus/RunContext.cs @@ -89,7 +89,7 @@ public static bool CreateRunContext(XTMFRuntime runtime, string id, byte[] model return true; } - private Stream? CreateRunBusLocal(ClientBus clientBus) + private Stream? CreateRunBusLocal(RunServerBus clientBus) { var pipeName = Guid.NewGuid().ToString(); string? error = null; @@ -109,14 +109,14 @@ public static bool CreateRunContext(XTMFRuntime runtime, string id, byte[] model return clientToRunStream; } - private (Stream clientToRunStream, Process runProcess) CreateRunBusRemote(ClientBus clientBus) + private (Stream clientToRunStream, Process runProcess) CreateRunBusRemote(RunServerBus clientBus) { var pipeName = Guid.NewGuid().ToString(); string? error = null; Process? runProcess = null; CreateStreams.CreateNewNamedPipeHost(pipeName, out var clientToRunStream, ref error, () => { - var path = Path.GetDirectoryName(typeof(ClientBus).GetTypeInfo().Assembly.Location)!; + var path = Path.GetDirectoryName(typeof(RunServerBus).GetTypeInfo().Assembly.Location)!; var startInfo = new ProcessStartInfo() { FileName = "dotnet", @@ -135,7 +135,7 @@ public static bool CreateRunContext(XTMFRuntime runtime, string id, byte[] model return (clientToRunStream!, runProcess!); } - private string GetExtraDlls(ClientBus client) + private string GetExtraDlls(RunServerBus client) { var builder = new StringBuilder(); foreach (var dll in client.ExtraDlls) @@ -148,7 +148,7 @@ private string GetExtraDlls(ClientBus client) } - public void RunInNewProcess(ClientBus client) + public void RunInNewProcess(RunServerBus client) { (Stream stream, Process runProcess) = CreateRunBusRemote(client); using var writer = new BinaryWriter(stream, Encoding.UTF8, false); @@ -162,7 +162,7 @@ public void RunInNewProcess(ClientBus client) runProcess.WaitForExit(); } - public void RunInCurrentProcess(ClientBus client) + public void RunInCurrentProcess(RunServerBus client) { using var stream = CreateRunBusLocal(client); new Run(ID, _modelSystem, StartToExecute, _runtime, _currentWorkingDirectory).StartRun(); diff --git a/src/XTMF2/Bus/ClientBus.cs b/src/XTMF2/Bus/RunServerBus.cs similarity index 98% rename from src/XTMF2/Bus/ClientBus.cs rename to src/XTMF2/Bus/RunServerBus.cs index 285834b..49568df 100644 --- a/src/XTMF2/Bus/ClientBus.cs +++ b/src/XTMF2/Bus/RunServerBus.cs @@ -30,7 +30,7 @@ namespace XTMF2.Bus /// Provides communication to the host and forwards communication /// to the Run. /// - public sealed class ClientBus : IDisposable + public sealed class RunServerBus : IDisposable { private readonly Stream _clientHost; private readonly bool _owner; @@ -55,7 +55,7 @@ public sealed class ClientBus : IDisposable /// A stream that connects to the host. /// Should this bus assume ownership over the stream? /// The XTMFRuntime to work within. - public ClientBus(Stream serverStream, bool streamOwner, XTMFRuntime runtime, List? extraDlls = null) + public RunServerBus(Stream serverStream, bool streamOwner, XTMFRuntime runtime, List? extraDlls = null) { Runtime = runtime; _runScheduler = new Scheduler(this); @@ -85,7 +85,7 @@ public void Dispose() Dispose(true); } - ~ClientBus() + ~RunServerBus() { Dispose(false); } diff --git a/src/XTMF2/Bus/Scheduler.cs b/src/XTMF2/Bus/Scheduler.cs index 6528e31..935fa33 100644 --- a/src/XTMF2/Bus/Scheduler.cs +++ b/src/XTMF2/Bus/Scheduler.cs @@ -29,7 +29,7 @@ namespace XTMF2.Bus internal sealed class Scheduler : IDisposable { private readonly ConcurrentQueue _ToRun = new ConcurrentQueue(); - private readonly ClientBus _Bus; + private readonly RunServerBus _Bus; private readonly CancellationTokenSource _CancelExecutionEngine; private readonly SemaphoreSlim _RunsToGo = new SemaphoreSlim(0); @@ -43,7 +43,7 @@ internal sealed class Scheduler : IDisposable /// Create a new Scheduler to process the given client bus. /// /// The bus to listen to. - public Scheduler(ClientBus bus) + public Scheduler(RunServerBus bus) { _Bus = bus; _CancelExecutionEngine = new CancellationTokenSource(); diff --git a/src/XTMF2/Controllers/ProjectController.cs b/src/XTMF2/Controllers/ProjectController.cs index 2af4b7f..e1dc2b3 100644 --- a/src/XTMF2/Controllers/ProjectController.cs +++ b/src/XTMF2/Controllers/ProjectController.cs @@ -24,6 +24,7 @@ You should have received a copy of the GNU General Public License using XTMF2.Repository; using System.Linq; using XTMF2.Editing; +using System.Diagnostics.CodeAnalysis; namespace XTMF2.Controllers { @@ -72,12 +73,11 @@ public static ReadOnlyObservableCollection GetProjects(User user) /// An editing session for this newly created project. /// An error message if the operation fails. /// True if the operation succeeds, false otherwise with an error message. - public bool CreateNewProject(User owner, string name, out ProjectSession? session, out CommandError? error) + public bool CreateNewProject(User owner, string name, [NotNullWhen(true)] out ProjectSession? session, [NotNullWhen(false)] out CommandError? error) { - if (owner is null) - { - throw new ArgumentNullException(nameof(owner)); - } + ArgumentNullException.ThrowIfNull(owner); + Helper.ThrowIfNullOrWhitespace(name); + session = null; if (!ValidateProjectName(name, out error)) { @@ -100,6 +100,41 @@ public bool CreateNewProject(User owner, string name, out ProjectSession? sessio } } + /// + /// Creates or gets a new project given a string for the name of the project. + /// + /// The user that owns the project. + /// The name of the project. + /// An editing session for this newly created project. + /// An error message if the operation fails. + /// True if the operation succeeds, false otherwise with an error message. + public bool CreateNewOrGet(User owner, string name, [NotNullWhen(true)] out ProjectSession? session, [NotNullWhen(false)] out CommandError? error) + { + ArgumentNullException.ThrowIfNull(owner); + Helper.ThrowIfNullOrWhitespace(name); + + session = null; + if (!ValidateProjectName(name, out error)) + { + return false; + } + lock (_controllerLock) + { + var userProjects = _projects.GetAvailableForUser(owner); + var p = userProjects.FirstOrDefault(project => project.Name!.Equals(name, StringComparison.OrdinalIgnoreCase)); + if (p is null) + { + if (!_projects.CreateNew(name, owner, out p, out error)) + { + return false; + } + owner.AddedUserToProject(p!); + } + session = GetSession(p!); + return true; + } + } + /// /// Import a project file. /// @@ -109,13 +144,10 @@ public bool CreateNewProject(User owner, string name, out ProjectSession? sessio /// An editing session for the imported project. /// An error message if the operation fails. /// True if the operation completes successfully, false otherwise with an error message. - public bool ImportProjectFile(User owner, string name, string filePath, out ProjectSession? session, out CommandError? error) + public bool ImportProjectFile(User owner, string name, string filePath, [NotNullWhen(true)] out ProjectSession? session, [NotNullWhen(false)] out CommandError? error) { session = null; - if (owner is null) - { - throw new ArgumentNullException(nameof(owner)); - } + ArgumentNullException.ThrowIfNull(owner); if (string.IsNullOrWhiteSpace(name)) { @@ -163,16 +195,11 @@ public bool ImportProjectFile(User owner, string name, string filePath, out Proj /// The resulting project session. /// An error message if the operation fails. /// True if the operation completes successfully, false otherwise with an error message. - public bool GetProjectSession(User user, Project project, out ProjectSession? session, out CommandError? error) + public bool GetProjectSession(User user, Project project, [NotNullWhen(true)] out ProjectSession? session, [NotNullWhen(false)] out CommandError? error) { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - if (project == null) - { - throw new ArgumentNullException(nameof(project)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(project); + lock(_controllerLock) { if(!project.CanAccess(user)) @@ -207,9 +234,9 @@ public bool GetProjectSession(User user, Project project, out ProjectSession? se if (projectFile != null) { string? error = null; - if (Project.Load(runtime.UserController, projectFile.FullName, out Project project, ref error)) + if (Project.Load(runtime.UserController, projectFile.FullName, out Project? project, ref error)) { - _projects.Add(project!, ref error); + _projects.Add(project, ref error); } else { @@ -231,16 +258,11 @@ public bool GetProjectSession(User user, Project project, out ProjectSession? se /// The resulting project reference. /// An error message if the operation fails. /// True if the operation completes successfully, false otherwise with an error message. - public bool GetProject(string userName, string projectName, out Project? project, out CommandError? error) + public bool GetProject(string userName, string projectName, [NotNullWhen(true)] out Project? project, [NotNullWhen(false)] out CommandError? error) { - if (String.IsNullOrWhiteSpace(userName)) - { - throw new ArgumentNullException(nameof(userName)); - } - if (String.IsNullOrWhiteSpace(projectName)) - { - throw new ArgumentNullException(nameof(projectName)); - } + Helper.ThrowIfNullOrWhitespace(userName); + Helper.ThrowIfNullOrWhitespace(projectName); + var user = _runtime.UserController.GetUserByName(userName); if(user == null) { @@ -261,16 +283,11 @@ public bool GetProject(string userName, string projectName, out Project? project /// The resulting project reference. /// An error message if the operation fails. /// True if the operation completes successfully, false otherwise with an error message. - public bool GetProject(User user, string projectName, out Project? project, out CommandError? error) + public bool GetProject(User user, string projectName, [NotNullWhen(true)] out Project? project, [NotNullWhen(false)] out CommandError? error) { - if(user == null) - { - throw new ArgumentNullException(nameof(user)); - } - if(String.IsNullOrWhiteSpace(projectName)) - { - throw new ArgumentNullException(nameof(projectName)); - } + ArgumentNullException.ThrowIfNull(user); + Helper.ThrowIfNullOrWhitespace(projectName); + lock (_controllerLock) { return _projects.GetProject(user, projectName, out project, out error); @@ -285,16 +302,11 @@ public bool GetProject(User user, string projectName, out Project? project, out /// The name of the project to delete. /// An error message if the operation fails. /// True if the operation completes successfully, false otherwise with an error message. - public bool DeleteProject(User user, string projectName, out CommandError? error) + public bool DeleteProject(User user, string projectName, [NotNullWhen(false)] out CommandError? error) { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - if (projectName == null) - { - throw new ArgumentNullException(nameof(projectName)); - } + ArgumentNullException.ThrowIfNull(user); + Helper.ThrowIfNullOrWhitespace(projectName); + lock (_controllerLock) { var project = GetProjects(user).FirstOrDefault(p => p.Name!.Equals(projectName, StringComparison.OrdinalIgnoreCase)); @@ -314,16 +326,11 @@ public bool DeleteProject(User user, string projectName, out CommandError? error /// The project to delete. /// An error message if the operation fails. /// True if the operation completes successfully, false otherwise with an error message. - public bool DeleteProject(User owner, Project project, out CommandError? error) + public bool DeleteProject(User owner, Project project, [NotNullWhen(false)] out CommandError? error) { - if (owner == null) - { - throw new ArgumentNullException(nameof(owner)); - } - if (project == null) - { - throw new ArgumentNullException(nameof(project)); - } + ArgumentNullException.ThrowIfNull(owner); + ArgumentNullException.ThrowIfNull(project); + lock (_controllerLock) { owner.RemovedUserForProject(project); @@ -345,17 +352,12 @@ internal void UnloadSession(ProjectSession projectSession) { var project = projectSession.Project; // The project will be null if we are running the model system. - if (!(project is null)) + if (project is not null) { _activeSessions.Remove(project); } } - private string GetPath(User owner, string name) - { - return Path.Combine(owner.UserPath, name); - } - private ProjectSession GetSession(Project project) { lock (_controllerLock) @@ -376,7 +378,7 @@ private ProjectSession GetSession(Project project) /// The name to validate /// A description of why the name was invalid. /// If the validation allows this project name. - public static bool ValidateProjectName(string name, ref string? error) + public static bool ValidateProjectName(string name, [NotNullWhen(false)] ref string? error) { if (String.IsNullOrWhiteSpace(name)) { @@ -398,7 +400,7 @@ public static bool ValidateProjectName(string name, ref string? error) /// The name to validate /// A description of why the name was invalid. /// If the validation allows this project name. - public static bool ValidateProjectName(string name, out CommandError? error) + public static bool ValidateProjectName(string name, [NotNullWhen(false)] out CommandError? error) { if (String.IsNullOrWhiteSpace(name)) { @@ -427,12 +429,10 @@ public static bool ValidateProjectName(string name, out CommandError? error) /// /// /// - public bool RenameProject(User user, Project project, string newProjectName, out CommandError? error) + public bool RenameProject(User user, Project project, string newProjectName, [NotNullWhen(false)] out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } + ArgumentNullException.ThrowIfNull(user); + if (!ValidateProjectName(newProjectName, out error)) { return false; diff --git a/src/XTMF2/Controllers/UserController.cs b/src/XTMF2/Controllers/UserController.cs index 6f9effa..7a3e8cd 100644 --- a/src/XTMF2/Controllers/UserController.cs +++ b/src/XTMF2/Controllers/UserController.cs @@ -19,6 +19,7 @@ You should have received a copy of the GNU General Public License using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text; @@ -51,9 +52,11 @@ public sealed class UserController /// The resulting user, null if the operation fails. /// An error message if the operation fails. /// True if the operation succeeds, false otherwise with an error message. - public bool CreateNew(string userName, bool admin, out User? user, out CommandError? error) + public bool CreateNew(string userName, bool admin, [NotNullWhen(true)] out User? user, [NotNullWhen(false)] out CommandError? error) { + Helper.ThrowIfNullOrWhitespace(userName); user = null; + if (!ValidateUserName(userName)) { error = new CommandError("Invalid name for a user."); @@ -62,7 +65,7 @@ public bool CreateNew(string userName, bool admin, out User? user, out CommandEr lock (UserLock) { //ensure there is no other user with the same name - if(_users.Any(u => u.UserName.Equals(userName, StringComparison.OrdinalIgnoreCase))) + if (_users.Any(u => u.UserName.Equals(userName, StringComparison.OrdinalIgnoreCase))) { error = new CommandError("A user with this name already exists."); return false; @@ -72,6 +75,41 @@ public bool CreateNew(string userName, bool admin, out User? user, out CommandEr } } + /// + /// Creates a user with the given name or gets the value + /// + /// The name to create the user with. + /// Should the user have administrative permissions. + /// The resulting user, null if the operation fails. + /// An error message if the operation fails. + /// True if the user was either created or retrieved. False otherwise with an error message. + public bool CreateOrGet(string userName, bool admin, [NotNullWhen(true)] out User? user, [NotNullWhen(false)] out CommandError? error) + { + Helper.ThrowIfNullOrWhitespace(userName); + user = null; + error = null; + + if (!ValidateUserName(userName)) + { + error = new CommandError("Invalid name for a user."); + return false; + } + lock (UserLock) + { + // Try to get the user first, if not create a new one. + user = _users.FirstOrDefault(u => u.UserName.Equals(userName, StringComparison.OrdinalIgnoreCase)); + if (user is not null) + { + return true; + } + else + { + _users.Add(user = new User(GetUserPath(userName), userName, admin)); + return user.Save(out error); + } + } + } + /// /// Delete a user given their user name /// @@ -79,10 +117,8 @@ public bool CreateNew(string userName, bool admin, out User? user, out CommandEr /// If the delete succeeds public bool Delete(string userName) { - if (userName == null) - { - throw new ArgumentNullException(nameof(userName)); - } + Helper.ThrowIfNullOrWhitespace(userName); + lock (UserLock) { var foundUser = _users.FirstOrDefault(user => user.UserName.Equals(userName, StringComparison.OrdinalIgnoreCase)); @@ -101,27 +137,24 @@ public bool Delete(string userName) /// True if the user was deleted public bool Delete(User user) { - if(user == null) - { - throw new ArgumentNullException(nameof(user)); - } + ArgumentNullException.ThrowIfNull(user); + var projectController = ProjectController; lock (UserLock) { - var userProjects = user.AvailableProjects; // make a copy of the projects to avoid altering a list // that is being enumerated foreach (var toDelete in (from p in userProjects - where user == p.Owner - select p ).ToList()) + where user == p.Owner + select p).ToList()) { projectController.DeleteProject(user, toDelete, out var error); } _users.Remove(user); // now remove all of the users files from the system. var userDir = new DirectoryInfo(user.UserPath); - if(userDir.Exists) + if (userDir.Exists) { userDir.Delete(true); } @@ -177,13 +210,13 @@ private void LoadUsers() // if the directory exists load it lock (UserLock) { - foreach(var potentialDir in usersDir.GetDirectories()) + foreach (var potentialDir in usersDir.GetDirectories()) { var userFile = potentialDir.GetFiles("User.xusr").FirstOrDefault(); - if(userFile != null) + if (userFile != null) { string? error = null; - if(User.Load(userFile.FullName, out var loadedUser, ref error)) + if (User.Load(userFile.FullName, out var loadedUser, ref error)) { _users.Add(loadedUser!); } @@ -192,9 +225,9 @@ private void LoadUsers() } } // if we have no users create a default user - if(_users.Count <= 0) + if (_users.Count <= 0) { - lock(UserLock) + lock (UserLock) { CreateInitialUser(); } diff --git a/src/XTMF2/Editing/EditingStack.cs b/src/XTMF2/Editing/EditingStack.cs index ef2b644..cca1426 100644 --- a/src/XTMF2/Editing/EditingStack.cs +++ b/src/XTMF2/Editing/EditingStack.cs @@ -58,10 +58,7 @@ public EditingStack(int capacity) /// The item may not be null. public void Add(CommandBatch item) { - if (item == null) - { - throw new ArgumentNullException(nameof(item)); - } + ArgumentNullException.ThrowIfNull(item); lock (_DataLock) { @@ -130,10 +127,7 @@ public void Clear() /// The item may not be null. public bool Contains(CommandBatch item) { - if (item == null) - { - throw new ArgumentNullException(nameof(item)); - } + ArgumentNullException.ThrowIfNull(item); lock (_DataLock) { @@ -159,10 +153,8 @@ public bool Contains(CommandBatch item) /// The array must be able to store all elements otherwise this error will be thrown. public void CopyTo(CommandBatch[] array, int arrayIndex) { - if(array == null) - { - throw new ArgumentNullException(nameof(array)); - } + ArgumentNullException.ThrowIfNull(array); + lock (_DataLock) { if(array.Length - arrayIndex < Count) diff --git a/src/XTMF2/Editing/ModelSystemSession.cs b/src/XTMF2/Editing/ModelSystemSession.cs index 8b77994..6e7452b 100644 --- a/src/XTMF2/Editing/ModelSystemSession.cs +++ b/src/XTMF2/Editing/ModelSystemSession.cs @@ -1,5 +1,5 @@ /* - Copyright 2017-2020 University of Toronto + Copyright 2017-2021 University of Toronto This file is part of XTMF2. XTMF2 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,6 +15,7 @@ You should have received a copy of the GNU General Public License using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text; @@ -68,14 +69,9 @@ internal ModuleRepository GetModuleRepository() public bool SetBoundaryName(User user, Boundary boundary, string name, out CommandError? error) { error = null; - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - if (boundary is null) - { - throw new ArgumentNullException(nameof(boundary)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(boundary); + if (String.IsNullOrWhiteSpace(name)) { error = new CommandError("A boundary requires a unique name"); @@ -114,14 +110,9 @@ public bool SetBoundaryName(User user, Boundary boundary, string name, out Comma /// True if the operation succeeds, false otherwise with an error message stored in error. public bool SetBoundaryDescription(User user, Boundary boundary, string description, out CommandError? error) { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - if (boundary == null) - { - throw new ArgumentNullException(nameof(boundary)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(boundary); + lock (_sessionLock) { if (!_session.HasAccess(user)) @@ -157,14 +148,9 @@ public bool SetBoundaryDescription(User user, Boundary boundary, string descript public bool AddBoundary(User user, Boundary parentBoundary, string name, out Boundary? boundary, out CommandError? error) { boundary = null; - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - if (parentBoundary == null) - { - throw new ArgumentNullException(nameof(parentBoundary)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(parentBoundary); + if (String.IsNullOrWhiteSpace(name)) { error = new CommandError("A boundary requires a unique name"); @@ -205,14 +191,9 @@ public bool AddCommentBlock(User user, Boundary boundary, string comment, Rectan out CommandError? error) { block = null; - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - if (boundary is null) - { - throw new ArgumentNullException(nameof(boundary)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(boundary); + if (String.IsNullOrWhiteSpace(comment)) { error = new CommandError("There was no comment to store."); @@ -259,20 +240,10 @@ internal void AddReference() /// True if the operation succeeds, false with an error message otherwise. public bool RemoveCommentBlock(User user, Boundary boundary, CommentBlock block, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (boundary is null) - { - throw new ArgumentNullException(nameof(boundary)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(boundary); + ArgumentNullException.ThrowIfNull(block); - if (block is null) - { - throw new ArgumentNullException(nameof(block)); - } lock (_sessionLock) { if (!_session.HasAccess(user)) @@ -305,15 +276,8 @@ public bool RemoveCommentBlock(User user, Boundary boundary, CommentBlock block, /// True if the operation succeeds, false otherwise with an error message. public bool SetCommentBlockLocation(User user, CommentBlock commentBlock, Rectangle newLocation, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (commentBlock is null) - { - throw new ArgumentNullException(nameof(commentBlock)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(commentBlock); lock (_sessionLock) { @@ -348,15 +312,8 @@ public bool SetCommentBlockLocation(User user, CommentBlock commentBlock, Rectan /// True if the operation succeeds, false otherwise with an error message. public bool SetCommentBlockText(User user, CommentBlock commentBlock, string newText, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (commentBlock is null) - { - throw new ArgumentNullException(nameof(commentBlock)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(commentBlock); if (string.IsNullOrEmpty(newText)) { @@ -396,18 +353,10 @@ public bool SetCommentBlockText(User user, CommentBlock commentBlock, string new /// True if the operation succeeds, false with an error message otherwise. public bool RemoveBoundary(User user, Boundary parentBoundary, Boundary boundary, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - if (parentBoundary is null) - { - throw new ArgumentNullException(nameof(parentBoundary)); - } - if (boundary is null) - { - throw new ArgumentNullException(nameof(boundary)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(parentBoundary); + ArgumentNullException.ThrowIfNull(boundary); + lock (_sessionLock) { if (!_session.HasAccess(user)) @@ -513,14 +462,9 @@ bool RemoveLinks(out CommandError? error2) /// True if the operation succeeds, false otherwise. public bool AddModelSystemStart(User user, Boundary boundary, string startName, Rectangle location, out Start? start, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - if (boundary is null) - { - throw new ArgumentNullException(nameof(boundary)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(boundary); + const string badStartName = "The start name must be unique within the model system and not empty."; start = null; if (String.IsNullOrWhiteSpace(startName)) @@ -565,14 +509,9 @@ public bool AddModelSystemStart(User user, Boundary boundary, string startName, /// public bool RemoveStart(User user, Start start, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - if (start is null) - { - throw new ArgumentNullException(nameof(start)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(start); + lock (_sessionLock) { if (!_session.HasAccess(user)) @@ -609,14 +548,9 @@ public bool RemoveStart(User user, Start start, out CommandError? error) /// True if the operation succeeds, false otherwise with an error message stored in error. public bool AddNode(User user, Boundary boundary, string name, Type type, Rectangle location, out Node? node, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - if (boundary is null) - { - throw new ArgumentNullException(nameof(boundary)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(boundary); + lock (_sessionLock) { if (!_session.HasAccess(user)) @@ -658,14 +592,9 @@ public bool AddNode(User user, Boundary boundary, string name, Type type, Rectan public bool AddNodeGenerateParameters(User user, Boundary boundary, string name, Type type, Rectangle location, out Node? node, out List? children, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - if (boundary is null) - { - throw new ArgumentNullException(nameof(boundary)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(boundary); + children = null; lock (_sessionLock) { @@ -750,7 +679,8 @@ void Remove() if (type.IsAssignableFrom(functionType)) { var child = Node.Create(this.GetModuleRepository(), hook.Name, functionType, boundary, Rectangle.Hidden); - if (child?.SetParameterValue(hook.DefaultValue!, out var error) == true) + + if (child?.SetParameterValue(ParameterExpression.CreateParameter(hook.DefaultValue!, genericParameters[0]), out var error) == true) { nodes.Add(child); if (boundary.AddLink(baseNode, hook, child, out var link, out error)) @@ -778,14 +708,9 @@ void Remove() /// True if the operation succeeds, false otherwise with an error message. public bool RemoveNode(User user, Node node, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - if (node is null) - { - throw new ArgumentNullException(nameof(node)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(node); + lock (_sessionLock) { if (!_session.HasAccess(user)) @@ -818,14 +743,9 @@ public bool RemoveNode(User user, Node node, out CommandError? error) /// True if the operation succeeds, False with an error message otherwise. public bool RemoveNodeGenerateParameters(User user, Node node, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - if (node is null) - { - throw new ArgumentNullException(nameof(node)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(node); + lock (_sessionLock) { if (!_session.HasAccess(user)) @@ -915,15 +835,8 @@ void AddParameters() public bool SetNodeLocation(User user, Node mss, Rectangle newLocation, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (mss is null) - { - throw new ArgumentNullException(nameof(mss)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(mss); lock (_sessionLock) { @@ -967,14 +880,9 @@ private List GetLinksGoingTo(Node destNode) /// True if the operation succeeds, false otherwise with an error message. public bool SetParameterValue(User user, Node basicParameter, string value, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - if (basicParameter is null) - { - throw new ArgumentNullException(nameof(basicParameter)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(basicParameter); + lock (_sessionLock) { if (!_session.HasAccess(user)) @@ -982,15 +890,16 @@ public bool SetParameterValue(User user, Node basicParameter, string value, out error = new CommandError("The user does not have access to this project.", true); return false; } - var previousValue = basicParameter.ParameterValue ?? string.Empty; - if (basicParameter.SetParameterValue(value, out error)) + var previousValue = basicParameter.ParameterValue; + var newValue = ParameterExpression.CreateParameter(value, basicParameter.Type.GetGenericArguments()[0]); + if (basicParameter.SetParameterValue(newValue, out error)) { Buffer.AddUndo(new Command(() => { - return (basicParameter.SetParameterValue(previousValue, out var e), e); + return (basicParameter.SetParameterValue(previousValue!, out var e), e); }, () => { - return (basicParameter.SetParameterValue(value, out var e), e); + return (basicParameter.SetParameterValue(newValue, out var e), e); })); return true; } @@ -998,6 +907,51 @@ public bool SetParameterValue(User user, Node basicParameter, string value, out } } + /// + /// Set the value of a parameter to an expression + /// + /// The user issuing the command + /// The parameter to set. + /// The value to set the parameter to. + /// An error message if the operation fails. + /// True if the operation succeeds, false otherwise with an error message. + public bool SetParameterExpression(User user, Node basicParameter, string expression, [NotNullWhen(false)]out CommandError? error) + { + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(basicParameter); + + lock(_sessionLock) + { + if (!_session.HasAccess(user)) + { + error = new CommandError("The user does not have access to this project.", true); + return false; + } + var previousType = basicParameter.Type; + var previousValue = basicParameter.ParameterValue; + if(basicParameter.SetParameterExpression(ModelSystem.Variables, expression, out error)) + { + var newType = basicParameter.Type; + var newExpression = basicParameter.ParameterValue; + Buffer.AddUndo(new Command(()=> + { + string? error = null; + _ = basicParameter.SetType(GetModuleRepository(), previousType, ref error); + _ = basicParameter.SetParameterValue(previousValue, out var e); + return (true, e); + }, ()=> + { + string? error = null; + _ = basicParameter.SetType(GetModuleRepository(), newType, ref error); + _ = basicParameter.SetParameterValue(newExpression, out var e); + return (true, e); + })); + return true; + } + return false; + } + } + /// /// Set the node to the disabled state. /// @@ -1008,14 +962,9 @@ public bool SetParameterValue(User user, Node basicParameter, string value, out /// True if the operation completed successfully, false otherwise. public bool SetNodeDisabled(User user, Node node, bool disabled, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - if (node is null) - { - throw new ArgumentNullException(nameof(node)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(node); + lock (_sessionLock) { if (!_session.HasAccess(user)) @@ -1049,14 +998,9 @@ public bool SetNodeDisabled(User user, Node node, bool disabled, out CommandErro /// True if the operation completed successfully, false otherwise. public bool SetLinkDisabled(User user, Link link, bool disabled, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - if (link is null) - { - throw new ArgumentNullException(nameof(link)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(link); + lock (_sessionLock) { if (!_session.HasAccess(user)) @@ -1129,14 +1073,9 @@ public bool Save(out CommandError? error, Stream saveTo) /// True if the operation succeeds, false otherwise with an error message. public bool RemoveLink(User user, Link link, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - if (link is null) - { - throw new ArgumentNullException(nameof(link)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(link); + lock (_sessionLock) { if (!_session.HasAccess(user)) @@ -1176,22 +1115,10 @@ public bool RemoveLink(User user, Link link, out CommandError? error) public bool AddLink(User user, Node origin, NodeHook originHook, Node destination, out Link? link, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - if (origin is null) - { - throw new ArgumentNullException(nameof(origin)); - } - if (originHook is null) - { - throw new ArgumentNullException(nameof(originHook)); - } - if (destination is null) - { - throw new ArgumentNullException(nameof(destination)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(origin); + ArgumentNullException.ThrowIfNull(originHook); + ArgumentNullException.ThrowIfNull(destination); link = null; lock (_sessionLock) @@ -1275,16 +1202,8 @@ public bool AddLink(User user, Node origin, NodeHook originHook, /// Thrown if the user or boundary are null. public bool AddFunctionTemplate(User user, Boundary boundary, string functionTemplateName, out FunctionTemplate? functionTemplate, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (boundary is null) - { - throw new ArgumentNullException(nameof(boundary)); - } - + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(boundary); error = null; functionTemplate = null; @@ -1327,22 +1246,11 @@ public bool AddFunctionTemplate(User user, Boundary boundary, string functionTem /// Thrown if the user, boundary, or function template are null. public bool RemoveFunctionTemplate(User user, Boundary boundary, FunctionTemplate functionTemplate, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (boundary is null) - { - throw new ArgumentNullException(nameof(boundary)); - } - - if (functionTemplate is null) - { - throw new ArgumentNullException(nameof(functionTemplate)); - } - + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(boundary); + ArgumentNullException.ThrowIfNull(functionTemplate); error = null; + lock (_sessionLock) { if (!_session.HasAccess(user)) @@ -1383,10 +1291,8 @@ internal static ModelSystemSession CreateRunSession(ProjectSession session, Mode /// True if the undo succeeds, false otherwise with an error message. public bool Undo(User user, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } + ArgumentNullException.ThrowIfNull(user); + lock (_sessionLock) { if (!_session.HasAccess(user)) @@ -1406,10 +1312,7 @@ public bool Undo(User user, out CommandError? error) /// True if the redo succeeds, false otherwise with an error message. public bool Redo(User user, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } + ArgumentNullException.ThrowIfNull(user); if (!_session.HasAccess(user)) { @@ -1429,15 +1332,10 @@ public bool Redo(User user, out CommandError? error) /// True if successful, false otherwise with error message. public bool RemoveLinkDestination(User user, Link multiLink, int index, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - if (multiLink is null) - { - throw new ArgumentNullException(nameof(multiLink)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(multiLink); error = null; + if (multiLink is MultiLink ml) { lock (_sessionLock) diff --git a/src/XTMF2/Editing/ProjectSession.cs b/src/XTMF2/Editing/ProjectSession.cs index ed881ef..f40f6e8 100644 --- a/src/XTMF2/Editing/ProjectSession.cs +++ b/src/XTMF2/Editing/ProjectSession.cs @@ -28,6 +28,7 @@ You should have received a copy of the GNU General Public License using System.IO.Compression; using System.Text.Json; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace XTMF2.Editing { @@ -118,10 +119,8 @@ internal ProjectSession AddReference() /// True if the user has access to the project, false otherwise. internal bool HasAccess(User user) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } + ArgumentNullException.ThrowIfNull(user); + lock (_sessionLock) { return Project.CanAccess(user); @@ -182,7 +181,7 @@ private void Dispose(bool managed) /// /// An error message if the save fails. /// True if the operation succeeds, false otherwise with an error message. - public bool Save(out CommandError? error) + public bool Save([NotNullWhen(false)] out CommandError? error) { lock (_sessionLock) { @@ -204,7 +203,7 @@ public bool Save(out CommandError? error) /// The resulting model system session /// An error message if the operation fails. /// True if the operation succeeds, false otherwise with an error message. - public bool CreateNewModelSystem(User user, string modelSystemName, out ModelSystemHeader? modelSystem, out CommandError? error) + public bool CreateNewModelSystem(User user, string modelSystemName, [NotNullWhen(true)] out ModelSystemHeader? modelSystem, [NotNullWhen(false)] out CommandError? error) { modelSystem = null; if (!ProjectController.ValidateProjectName(modelSystemName, out error)) @@ -229,23 +228,51 @@ public bool CreateNewModelSystem(User user, string modelSystemName, out ModelSys } /// - /// Create a model system session allowing for the editing of a model system. + /// Create a new model system with the given name. The name must be unique. /// - /// The user that is requesting access. - /// The model system reference to load. - /// The resulting session. + /// The name of the model system (must be unique within the project). + /// The resulting model system session /// An error message if the operation fails. /// True if the operation succeeds, false otherwise with an error message. - public bool EditModelSystem(User user, ModelSystemHeader modelSystemHeader, out ModelSystemSession? session, out CommandError? error) + public bool CreateOrGetModelSystem(User user, string modelSystemName, [NotNullWhen(true)] out ModelSystemHeader? modelSystem, [NotNullWhen(false)] out CommandError? error) { - if (user is null) + modelSystem = null; + if (!ProjectController.ValidateProjectName(modelSystemName, out error)) { - throw new ArgumentNullException(nameof(user)); + return false; } - if (modelSystemHeader is null) + lock (_sessionLock) { - throw new ArgumentNullException(nameof(modelSystemHeader)); + if (!Project.CanAccess(user)) + { + error = new CommandError("The user does not have access to this project!", true); + return false; + } + if(Project.GetModelSystemHeader(modelSystemName, out modelSystem, out error)) + { + return true; + } + else + { + modelSystem = new ModelSystemHeader(Project, modelSystemName); + return Project.Add(this, modelSystem, out error); + } } + } + + /// + /// Create a model system session allowing for the editing of a model system. + /// + /// The user that is requesting access. + /// The model system reference to load. + /// The resulting session. + /// An error message if the operation fails. + /// True if the operation succeeds, false otherwise with an error message. + public bool EditModelSystem(User user, ModelSystemHeader modelSystemHeader, [NotNullWhen(true)] out ModelSystemSession? session, [NotNullWhen(false)] out CommandError? error) + { + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(modelSystemHeader); + session = null; lock (_sessionLock) { @@ -285,17 +312,10 @@ public bool EditModelSystem(User user, ModelSystemHeader modelSystemHeader, out /// The model system to remove. /// An error message if the operation fails. /// True if the operation succeeds, false otherwise with an error message. - public bool RemoveModelSystem(User user, ModelSystemHeader modelSystem, out CommandError? error) + public bool RemoveModelSystem(User user, ModelSystemHeader modelSystem, [NotNullWhen(false)] out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (modelSystem is null) - { - throw new ArgumentNullException(nameof(modelSystem)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(modelSystem); lock (_sessionLock) { @@ -334,17 +354,10 @@ public bool RemoveModelSystem(User user, ModelSystemHeader modelSystem, out Comm /// The file that the project will be saved as. /// An error message if the operation fails. /// True if the operation succeeds, false otherwise with an error message. - public bool ExportProject(User user, string exportPath, out CommandError? error) + public bool ExportProject(User user, string exportPath, [NotNullWhen(false)] out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (string.IsNullOrWhiteSpace(exportPath)) - { - throw new ArgumentException("message", nameof(exportPath)); - } + ArgumentNullException.ThrowIfNull(user); + Helper.ThrowIfNullOrWhitespace(exportPath); lock (_sessionLock) { @@ -370,17 +383,10 @@ public bool ExportProject(User user, string exportPath, out CommandError? error) /// The location to export the model system to. /// An error message if the operation fails. /// True if the operation succeeds, false otherwise with error message. - public bool ExportModelSystem(User user, ModelSystemHeader modelSystemHeader, string exportPath, out CommandError? error) + public bool ExportModelSystem(User user, ModelSystemHeader modelSystemHeader, string exportPath, [NotNullWhen(false)] out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (modelSystemHeader is null) - { - throw new ArgumentNullException(nameof(modelSystemHeader)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(modelSystemHeader); if (string.IsNullOrWhiteSpace(exportPath)) { @@ -411,16 +417,11 @@ public bool ExportModelSystem(User user, ModelSystemHeader modelSystemHeader, st /// The resulting model system header. /// An error message if the operation fails. /// True if the operation succeeds, false otherwise with an error message. - public bool GetModelSystemHeader(User user, string modelSystemName, out ModelSystemHeader? modelSystemHeader, out CommandError? error) + public bool GetModelSystemHeader(User user, string modelSystemName, [NotNullWhen(true)] out ModelSystemHeader? modelSystemHeader, [NotNullWhen(false)] out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - if (String.IsNullOrWhiteSpace(modelSystemName)) - { - throw new ArgumentNullException(nameof(modelSystemName)); - } + ArgumentNullException.ThrowIfNull(user); + Helper.ThrowIfNullOrWhitespace(modelSystemName); + lock (_sessionLock) { if (!Project.CanAccess(user)) @@ -437,20 +438,14 @@ public bool GetModelSystemHeader(User user, string modelSystemName, out ModelSys /// Share the project with the given user /// /// The user that is issuing the share command - /// The person to share with + /// The person to share with /// An error message if appropriate /// True if the operation succeeds, false otherwise with an error message. - public bool ShareWith(User doingShare, User toSharWith, out CommandError? error) + public bool ShareWith(User doingShare, User toShareWith, [NotNullWhen(false)] out CommandError? error) { - // test our arguments - if (doingShare is null) - { - throw new ArgumentNullException(nameof(doingShare)); - } - if (toSharWith is null) - { - throw new ArgumentNullException(nameof(doingShare)); - } + ArgumentNullException.ThrowIfNull(doingShare); + ArgumentNullException.ThrowIfNull(toShareWith); + lock (_sessionLock) { if (!(doingShare.IsAdmin || doingShare == Project.Owner)) @@ -459,7 +454,7 @@ public bool ShareWith(User doingShare, User toSharWith, out CommandError? error) return false; } // now that we know that we can do the share - return Project.AddAdditionalUser(toSharWith, out error); + return Project.AddAdditionalUser(toShareWith, out error); } } @@ -480,16 +475,11 @@ internal static ProjectSession CreateRunSession(XTMFRuntime runtime) /// /// /// True if the operation succeeds, false otherwise with an error message. - public bool SwitchOwner(User owner, User newOwner, out CommandError? error) + public bool SwitchOwner(User owner, User newOwner, [NotNullWhen(false)] out CommandError? error) { - if (owner is null) - { - throw new ArgumentNullException(nameof(owner)); - } - if (newOwner is null) - { - throw new ArgumentNullException(nameof(newOwner)); - } + ArgumentNullException.ThrowIfNull(owner); + ArgumentNullException.ThrowIfNull(newOwner); + lock (_sessionLock) { if (!(owner.IsAdmin || owner == Project.Owner)) @@ -508,16 +498,11 @@ public bool SwitchOwner(User owner, User newOwner, out CommandError? error) /// The user to remove access to. /// An error message if the operation fails. /// True if the operation succeeds, false otherwise with an error message. - public bool RestrictAccess(User owner, User toRestrict, out CommandError? error) + public bool RestrictAccess(User owner, User toRestrict, [NotNullWhen(false)] out CommandError? error) { - if (owner is null) - { - throw new ArgumentNullException(nameof(owner)); - } - if (toRestrict is null) - { - throw new ArgumentNullException(nameof(toRestrict)); - } + ArgumentNullException.ThrowIfNull(owner); + ArgumentNullException.ThrowIfNull(toRestrict); + lock (_sessionLock) { if (!(owner.IsAdmin || owner == Project.Owner)) @@ -544,23 +529,13 @@ public bool RestrictAccess(User owner, User toRestrict, out CommandError? error) /// The error message if the operation fails. /// True if the operation succeeds, false otherwise with an error message. public bool ImportModelSystem(User user, string modelSystemFilePath, string modelSystemName, - out ModelSystemHeader? header, out CommandError? error) + [NotNullWhen(true)] out ModelSystemHeader? header, [NotNullWhen(false)] out CommandError? error) { header = null; - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (string.IsNullOrWhiteSpace(modelSystemFilePath)) - { - throw new ArgumentException(nameof(modelSystemFilePath)); - } + ArgumentNullException.ThrowIfNull(user); + Helper.ThrowIfNullOrWhitespace(modelSystemFilePath); + Helper.ThrowIfNullOrWhitespace(modelSystemName); - if (string.IsNullOrWhiteSpace(modelSystemName)) - { - throw new ArgumentException(nameof(modelSystemName)); - } try { using var archive = ZipFile.OpenRead(modelSystemFilePath); @@ -601,12 +576,9 @@ public bool ImportModelSystem(User user, string modelSystemFilePath, string mode /// The path to where to store runs. /// An error message if the operation fails. /// True if the operation succeeds, false otherwise with an error message. - public bool SetCustomRunDirectory(User user, string fullName, out CommandError? error) + public bool SetCustomRunDirectory(User user, string fullName, [NotNullWhen(false)] out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } + ArgumentNullException.ThrowIfNull(user); if (string.IsNullOrWhiteSpace(fullName)) { @@ -630,12 +602,10 @@ public bool SetCustomRunDirectory(User user, string fullName, out CommandError? /// The user issuing the command. /// An error message if the operation fails. /// True if the operation succeeds, false otherwise with an error message. - public bool ResetCustomRunDirectory(User user, out CommandError? error) + public bool ResetCustomRunDirectory(User user, [NotNullWhen(false)] out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } + ArgumentNullException.ThrowIfNull(user); + lock (_sessionLock) { if (!Project.CanAccess(user)) @@ -655,16 +625,11 @@ public bool ResetCustomRunDirectory(User user, out CommandError? error) /// The new name of the model system. /// An error message if the operation fails. /// True if the operation succeeds, false otherwise with an error message. - public bool RenameModelSystem(User user, ModelSystemHeader modelSystem, string newName, out CommandError? error) + public bool RenameModelSystem(User user, ModelSystemHeader modelSystem, string newName, [NotNullWhen(false)] out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } - if (modelSystem is null) - { - throw new ArgumentNullException(nameof(modelSystem)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(modelSystem); + if (string.IsNullOrWhiteSpace(newName)) { error = new CommandError("The name of the model system must not be blank."); @@ -688,12 +653,10 @@ public bool RenameModelSystem(User user, ModelSystemHeader modelSystem, string n /// The path to add. /// An error message if the operation fails. /// True if the operation succeeds, false otherwise with an error message. - public bool AddAdditionalPastRunDirectory(User user, string pastRunDirectoryPath, out CommandError? error) + public bool AddAdditionalPastRunDirectory(User user, string pastRunDirectoryPath, [NotNullWhen(false)] out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } + ArgumentNullException.ThrowIfNull(user); + if (String.IsNullOrWhiteSpace(pastRunDirectoryPath)) { error = new CommandError("The additional past run directory path was invalid."); @@ -717,12 +680,10 @@ public bool AddAdditionalPastRunDirectory(User user, string pastRunDirectoryPath /// The path to add. /// An error message if the operation fails. /// True if the operation succeeds, false otherwise with an error message. - public bool RemoveAdditionalPastRunDirectory(User user, string pastRunDirectoryPath, out CommandError? error) + public bool RemoveAdditionalPastRunDirectory(User user, string pastRunDirectoryPath, [NotNullWhen(false)] out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } + ArgumentNullException.ThrowIfNull(user); + if (String.IsNullOrWhiteSpace(pastRunDirectoryPath)) { error = new CommandError("The additional past run directory path was invalid."); diff --git a/src/XTMF2/Helper.cs b/src/XTMF2/Helper.cs index 887d9c8..2d815b7 100644 --- a/src/XTMF2/Helper.cs +++ b/src/XTMF2/Helper.cs @@ -18,6 +18,7 @@ You should have received a copy of the GNU General Public License */ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Text; namespace XTMF2 @@ -36,7 +37,7 @@ public static class Helper public static bool UsingIf(this bool callResult, T disposable, Action disposableToExecuteOnSuccess) where T : IDisposable { - if(callResult) + if (callResult) { using (disposable) { @@ -61,5 +62,19 @@ public static void UsingIf(this bool callResult, T disposable, Action disposa onFailure(); } } + + /// + /// Throw an exception is the string is null empty or whitespace + /// + /// The string to test. + /// The name of the parameter. Automatically set to the name of the argument. + /// Thrown if the argument is null, empty, or only contains whitespace. + public static void ThrowIfNullOrWhitespace(string? argument, [CallerArgumentExpression("argument")] string? paramName = null) + { + if (string.IsNullOrWhiteSpace(argument)) + { + throw new ArgumentNullException(paramName); + } + } } } diff --git a/src/XTMF2/ModelSystemConstruct/Boundary.cs b/src/XTMF2/ModelSystemConstruct/Boundary.cs index fc0502f..46e14f4 100644 --- a/src/XTMF2/ModelSystemConstruct/Boundary.cs +++ b/src/XTMF2/ModelSystemConstruct/Boundary.cs @@ -20,6 +20,7 @@ You should have received a copy of the GNU General Public License using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Text.Json; @@ -632,8 +633,8 @@ internal bool AddLink(Link link, out CommandError? e) - internal bool Load(ModuleRepository modules, Dictionary typeLookup, Dictionary node, - ref Utf8JsonReader reader, ref string? error) + internal bool Load(ModuleRepository modules, Dictionary typeLookup, Dictionary node, List<(Node toAssignTo, string parameterExpression)> scriptedParameters, + ref Utf8JsonReader reader, [NotNullWhen(false)] ref string? error) { if (reader.TokenType != JsonTokenType.StartObject) { @@ -692,7 +693,7 @@ internal bool Load(ModuleRepository modules, Dictionary typeLookup, D { if (reader.TokenType != JsonTokenType.Comment) { - if (!Node.Load(modules, typeLookup, node, this, ref reader, out var mss, ref error)) + if (!Node.Load(modules, typeLookup, node, scriptedParameters, this, ref reader, out var mss, ref error)) { return false; } @@ -711,7 +712,7 @@ internal bool Load(ModuleRepository modules, Dictionary typeLookup, D if (reader.TokenType != JsonTokenType.Comment) { var boundary = new Boundary(this); - if (!boundary.Load(modules, typeLookup, node, ref reader, ref error)) + if (!boundary.Load(modules, typeLookup, node, scriptedParameters, ref reader, ref error)) { return false; } @@ -764,7 +765,7 @@ internal bool Load(ModuleRepository modules, Dictionary typeLookup, D { if(reader.TokenType != JsonTokenType.Comment) { - if(!FunctionTemplate.Load(modules, typeLookup, node, ref reader, this, out var template, ref error)) + if(!FunctionTemplate.Load(modules, typeLookup, node, scriptedParameters, ref reader, this, out var template, ref error)) { return false; } diff --git a/src/XTMF2/ModelSystemConstruct/CommentBlock.cs b/src/XTMF2/ModelSystemConstruct/CommentBlock.cs index 956fbdf..cbb819c 100644 --- a/src/XTMF2/ModelSystemConstruct/CommentBlock.cs +++ b/src/XTMF2/ModelSystemConstruct/CommentBlock.cs @@ -19,6 +19,7 @@ You should have received a copy of the GNU General Public License using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Text; using System.Text.Json; using XTMF2.Editing; @@ -98,7 +99,7 @@ internal void Save(Utf8JsonWriter writer) writer.WriteEndObject(); } - internal static bool Load(ref Utf8JsonReader reader, out CommentBlock? block, ref string? error) + internal static bool Load(ref Utf8JsonReader reader, [NotNullWhen(true)] out CommentBlock? block, [NotNullWhen(false)] ref string? error) { float x = 0, y = 0, width = 0, height = 0; string comment = "No comment"; diff --git a/src/XTMF2/ModelSystemConstruct/Expression.cs b/src/XTMF2/ModelSystemConstruct/Expression.cs new file mode 100644 index 0000000..e02e192 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Expression.cs @@ -0,0 +1,68 @@ +/* + Copyright 2022 University of Toronto + This file is part of XTMF2. + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; + +namespace XTMF2.ModelSystemConstruct; + +/// +/// This class represents a calculation +/// +public abstract class Expression +{ + /// + /// The string representation of this expression. + /// + protected readonly ReadOnlyMemory Text; + + /// + /// The offset into the full expression that this starts at. + /// + protected readonly int Offset; + + /// + /// + /// + /// The text form of the expression that this represents. + /// The offset into the full expression that we start at. + protected Expression(ReadOnlyMemory expression, int offset) + { + Text = expression; + Offset = offset; + } + + /// + /// Gets the string representation of the expression. + /// + /// The string representation of the expression. + public ReadOnlySpan AsString() + { + return Text.Span; + } + + /// + /// The type that the expression will evaluate to. + /// + public abstract Type Type { get; } + + /// + /// Gets a result with the literal's value. + /// + /// The result that this literal represents. + /// The module that is asking for this expression to be resolved. + internal abstract Result GetResult(IModule caller); +} diff --git a/src/XTMF2/ModelSystemConstruct/FunctionTemplate.cs b/src/XTMF2/ModelSystemConstruct/FunctionTemplate.cs index 657b7c6..84efe2b 100644 --- a/src/XTMF2/ModelSystemConstruct/FunctionTemplate.cs +++ b/src/XTMF2/ModelSystemConstruct/FunctionTemplate.cs @@ -19,6 +19,7 @@ You should have received a copy of the GNU General Public License using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Text; using System.Text.Json; using XTMF2.Editing; @@ -98,8 +99,8 @@ internal void Save(ref int index, Dictionary nodeDictionary, Dictiona /// The function template that was created by loading the file. /// An error message if we failed to load the function template or its children. /// True if the operation succeeded, false otherwise with an error message. - internal static bool Load(ModuleRepository modules, Dictionary typeLookup, Dictionary node, - ref Utf8JsonReader reader, Boundary parent, out FunctionTemplate? template, ref string? error) + internal static bool Load(ModuleRepository modules, Dictionary typeLookup, Dictionary node, List<(Node toAssignTo, string parameterExpression)> scriptedParameters, + ref Utf8JsonReader reader, Boundary parent, [NotNullWhen(true)] out FunctionTemplate? template, [NotNullWhen(false)] ref string? error) { template = null; string? name = null; @@ -122,7 +123,7 @@ internal static bool Load(ModuleRepository modules, Dictionary typeLo else if(reader.ValueTextEquals(nameof(InternalModules))) { reader.Read(); - if(!innerModules.Load(modules, typeLookup, node, ref reader, ref error)) + if(!innerModules.Load(modules, typeLookup, node, scriptedParameters, ref reader, ref error)) { return false; } diff --git a/src/XTMF2/ModelSystemConstruct/Link.cs b/src/XTMF2/ModelSystemConstruct/Link.cs index ce8c700..b1859c1 100644 --- a/src/XTMF2/ModelSystemConstruct/Link.cs +++ b/src/XTMF2/ModelSystemConstruct/Link.cs @@ -25,6 +25,7 @@ You should have received a copy of the GNU General Public License using System.Linq; using XTMF2.ModelSystemConstruct; using XTMF2.Repository; +using System.Diagnostics.CodeAnalysis; namespace XTMF2 { @@ -71,7 +72,7 @@ private static bool FailWith(out Link? link, out string error, string message) return false; } - internal static bool Create(ModuleRepository modules, Dictionary nodes, ref Utf8JsonReader reader, out Link? link, ref string? error) + internal static bool Create(ModuleRepository modules, Dictionary nodes, ref Utf8JsonReader reader, [NotNullWhen(true)] out Link? link, [NotNullWhen(false)] ref string? error) { if(reader.TokenType != JsonTokenType.StartObject) { diff --git a/src/XTMF2/ModelSystemConstruct/ModelSystem.cs b/src/XTMF2/ModelSystemConstruct/ModelSystem.cs index ef21ff3..6cff1ed 100644 --- a/src/XTMF2/ModelSystemConstruct/ModelSystem.cs +++ b/src/XTMF2/ModelSystemConstruct/ModelSystem.cs @@ -27,6 +27,8 @@ You should have received a copy of the GNU General Public License using System.IO; using XTMF2.Repository; using XTMF2.ModelSystemConstruct; +using System.Diagnostics.CodeAnalysis; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; namespace XTMF2 { @@ -82,6 +84,11 @@ private void Header_PropertyChanged(object? sender, PropertyChangedEventArgs e) /// internal ModelSystemHeader Header { get; private set; } + /// + /// The nodes which are allowed to be used as a variable for parameter expressions + /// + public IList Variables { get; internal set; } = Array.Empty(); + private const string GlobalBoundaryName = "global"; private const string IndexProperty = "Index"; private const string TypeProperty = "Type"; @@ -225,7 +232,7 @@ internal bool Contains(Boundary boundary) } } - internal static bool Load(string modelSystem, XTMFRuntime runtime, out ModelSystem? ms, ref string? error) + internal static bool Load(string modelSystem, XTMFRuntime runtime, [NotNullWhen(true)] out ModelSystem? ms, [NotNullWhen(false)] ref string? error) { using var stream = new MemoryStream(Encoding.Unicode.GetBytes(modelSystem)); var header = ModelSystemHeader.CreateRunHeader(runtime); @@ -233,7 +240,7 @@ internal static bool Load(string modelSystem, XTMFRuntime runtime, out ModelSyst return ms != null; } - internal static bool Load(ProjectSession session, ModelSystemHeader modelSystemHeader, out ModelSystemSession? msSession, out CommandError? error) + internal static bool Load(ProjectSession session, ModelSystemHeader modelSystemHeader, [NotNullWhen(true)] out ModelSystemSession? msSession, [NotNullWhen(false)] out CommandError? error) { // the parameters are have already been vetted var path = modelSystemHeader.ModelSystemPath; @@ -272,7 +279,8 @@ internal static bool Load(ProjectSession session, ModelSystemHeader modelSystemH } } - private static ModelSystem? Load(Stream rawStream, ModuleRepository modules, ModelSystemHeader modelSystemHeader, ref string? error) + private static ModelSystem? Load(Stream rawStream, ModuleRepository modules, ModelSystemHeader modelSystemHeader, + [NotNullWhen(false)] ref string? error) { try { @@ -282,6 +290,7 @@ internal static bool Load(ProjectSession session, ModelSystemHeader modelSystemH var reader = new Utf8JsonReader(stream.GetBuffer().AsSpan()); var typeLookup = new Dictionary(); var nodes = new Dictionary(); + List<(Node toAssignTo, string parameterExpression)> scriptedParameters = new(); while (reader.Read()) { if (reader.TokenType == JsonTokenType.PropertyName) @@ -295,7 +304,7 @@ internal static bool Load(ProjectSession session, ModelSystemHeader modelSystemH } else if (reader.ValueTextEquals(BoundariesProperty)) { - if (!LoadBoundaries(modules, typeLookup, nodes, ref reader, modelSystem.GlobalBoundary, ref error)) + if (!LoadBoundaries(modules, typeLookup, nodes, scriptedParameters, ref reader, modelSystem.GlobalBoundary, ref error)) { return null; } @@ -308,6 +317,14 @@ internal static bool Load(ProjectSession session, ModelSystemHeader modelSystemH } } } + // Now that all of the modules have been loaded we can process the scripted parameters + foreach (var (toAssignTo, parameterExpression) in scriptedParameters) + { + if(!toAssignTo.SetParameterExpression(modelSystem.Variables, parameterExpression, out CommandError? cmdError)) + { + // TODO: Think about what to do in order to heal the model system + } + } return modelSystem; } catch (JsonException e) @@ -327,7 +344,7 @@ private static bool FailWith(out string error, string message) return false; } - private static bool LoadTypes(Dictionary typeLookup, ref Utf8JsonReader reader, ref string? error) + private static bool LoadTypes(Dictionary typeLookup, ref Utf8JsonReader reader, [NotNullWhen(false)] ref string? error) { if (!reader.Read() || reader.TokenType != JsonTokenType.StartArray) { @@ -381,13 +398,19 @@ private static bool LoadTypes(Dictionary typeLookup, ref Utf8JsonRead } private static bool LoadBoundaries(ModuleRepository modules, Dictionary typeLookup, Dictionary nodes, - ref Utf8JsonReader reader, Boundary global, ref string? error) + List<(Node toAssignTo, string parameterExpression)> scriptedParameters, ref Utf8JsonReader reader, Boundary global, [NotNullWhen(false)] ref string? error) { if (!reader.Read() || reader.TokenType != JsonTokenType.StartArray) { return FailWith(out error, "Expected to read an array when loading boundaries!"); } - if (!reader.Read() || !global.Load(modules, typeLookup, nodes, ref reader, ref error)) + + if (!reader.Read()) + { + return FailWith(out error, "Unexpected end of file when loading boundaries!"); + } + + if(!global.Load(modules, typeLookup, nodes, scriptedParameters, ref reader, ref error)) { return false; } diff --git a/src/XTMF2/ModelSystemConstruct/Node.cs b/src/XTMF2/ModelSystemConstruct/Node.cs index a2c47f5..2b7c3c6 100644 --- a/src/XTMF2/ModelSystemConstruct/Node.cs +++ b/src/XTMF2/ModelSystemConstruct/Node.cs @@ -23,6 +23,8 @@ You should have received a copy of the GNU General Public License using System.Collections.Concurrent; using System.Linq; using XTMF2.Repository; +using System.Diagnostics.CodeAnalysis; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; namespace XTMF2.ModelSystemConstruct { @@ -45,6 +47,7 @@ public class Node : INotifyPropertyChanged protected const string HeightProperty = "Height"; protected const string IndexProperty = "Index"; protected const string ParameterProperty = "Parameter"; + protected const string ParameterExpressionProperty = "ParameterExpression"; protected const string DisabledProperty = "Disabled"; /// @@ -61,7 +64,7 @@ public class Node : INotifyPropertyChanged /// /// A parameter value to use if this is a parameter type /// - public string? ParameterValue { get; private set; } + public ParameterExpression? ParameterValue { get; private set; } /// /// Create the hooks for the node @@ -130,7 +133,7 @@ internal bool SetName(string name, out CommandError? error) /// The value to change the parameter to. /// A description of the error if one occurs /// True if the operation was successful, false otherwise - internal bool SetParameterValue(string value, out CommandError? error) + internal bool SetParameterValue(ParameterExpression? value, out CommandError? error) { // ensure that the value is allowed if (Type == null) @@ -138,9 +141,12 @@ internal bool SetParameterValue(string value, out CommandError? error) return FailWith(out error, "Unable to set the parameter value of a node that lacks a type!"); } string? errorString = null; - if (!ArbitraryParameterParser.Check(Type.GenericTypeArguments[0], value, ref errorString)) + if (value is not null) { - return FailWith(out error, $"Unable to create a parse the value {value} for type {Type.GenericTypeArguments[0].FullName}!"); + if (!value.IsCompatible(Type.GenericTypeArguments[0], ref errorString)) + { + return FailWith(out error, $"Unable to create a parse the value {value} for type {Type.GenericTypeArguments[0].FullName}!"); + } } ParameterValue = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ParameterValue))); @@ -148,6 +154,38 @@ internal bool SetParameterValue(string value, out CommandError? error) return true; } + /// + /// Set the value of a parameter that is an expression. This will + /// modify the node's type if required. + /// + /// The nodes that can be used as a variable. + /// The value to change the parameter to. + /// A description of the error if one occurs + /// True if the operation was successful, false otherwise + internal bool SetParameterExpression(IList variables, string value, [NotNullWhen(false)] out CommandError? error) + { + // ensure that the value is allowed + if (Type == null) + { + return FailWith(out error, "Unable to set the parameter value of a node that lacks a type!"); + } + string? errorString = null; + // TODO: Add in the logic for how to select what nodes are available to use as variables + if (!ParameterCompiler.CreateExpression(variables, value, out var expression, ref errorString)) + { + return FailWith(out error, errorString); + } + var parameterExpression = ParameterExpression.CreateParameter(expression); + if (!parameterExpression.IsCompatible(Type.GenericTypeArguments[0], ref errorString)) + { + return FailWith(out error, $"Unable to create a parse the value {value} for type {Type.GenericTypeArguments[0].FullName}!"); + } + ParameterValue = parameterExpression; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ParameterValue))); + error = null; + return true; + } + internal bool Validate(ref string? moduleName, ref string? error) { foreach (var hook in Hooks) @@ -197,10 +235,9 @@ internal bool ConstructModule(XTMFRuntime runtime, ref string? error) return FailWith(out error, $"Unable to construct a module named {Name} without a type!"); } var typeInfo = _type.GetTypeInfo(); - var module = ( + if (( typeInfo.GetConstructor(RuntimeConstructor)?.Invoke(new[] { runtime }) - ?? typeInfo.GetConstructor(EmptyConstructor)?.Invoke(EmptyConstructor)) as IModule; - if (!(module is IModule)) + ?? typeInfo.GetConstructor(EmptyConstructor)?.Invoke(EmptyConstructor)) is not IModule module) { return FailWith(out error, $"Unable to construct a module of type {_type.GetTypeInfo().AssemblyQualifiedName}!"); } @@ -209,22 +246,20 @@ internal bool ConstructModule(XTMFRuntime runtime, ref string? error) if (_type.IsConstructedGenericType && _type.GetGenericTypeDefinition() == GenericParameter) { var paramType = _type.GenericTypeArguments[0]; - var paramValue = ParameterValue ?? ""; - var (Sucess, Value) = ArbitraryParameterParser.ArbitraryParameterParse(paramType, paramValue, ref error); - if (!Sucess) - { - return FailWith(out error, $"Unable to assign the value of {paramValue} to type {paramType.FullName}!"); - } - if (!GenericValue.TryGetValue(_type, out var info)) + var paramValue = ParameterValue?.GetValue(module, paramType, ref error); + if (paramValue is not null) { - info = _type.GetRuntimeField("Value"); - if (info == null) + if (!GenericValue.TryGetValue(_type, out var info)) { - return FailWith(out error, $"Unable find a field named 'Value' on type {_type.FullName} in order to assign a value to it!"); + info = _type.GetRuntimeField("Value"); + if (info == null) + { + return FailWith(out error, $"Unable find a field named 'Value' on type {_type.FullName} in order to assign a value to it!"); + } + GenericValue[paramType] = info; } - GenericValue[paramType] = info; + info.SetValue(Module, paramValue); } - info.SetValue(Module, Value); } error = null; return true; @@ -344,9 +379,9 @@ internal virtual void Save(ref int index, Dictionary moduleDictionary writer.WriteNumber(WidthProperty, Location.Width); writer.WriteNumber(HeightProperty, Location.Height); writer.WriteNumber(IndexProperty, index++); - if (!String.IsNullOrEmpty(ParameterValue)) + if (ParameterValue is not null) { - writer.WriteString(ParameterProperty, ParameterValue); + ParameterValue.Save(writer); } if (IsDisabled) { @@ -355,8 +390,8 @@ internal virtual void Save(ref int index, Dictionary moduleDictionary writer.WriteEndObject(); } - internal static bool Load(ModuleRepository modules, Dictionary typeLookup, Dictionary nodes, - Boundary boundary, ref Utf8JsonReader reader, out Node? mss, ref string? error) + internal static bool Load(ModuleRepository modules, Dictionary typeLookup, Dictionary nodes, List<(Node toAssignTo, string parameterExpression)> scriptedParameters, + Boundary boundary, ref Utf8JsonReader reader, out Node? mss, [NotNullWhen(false)] ref string? error) { if (reader.TokenType != JsonTokenType.StartObject) { @@ -368,7 +403,8 @@ internal static bool Load(ModuleRepository modules, Dictionary typeLo bool disabled = false; Rectangle point = new Rectangle(); string description = string.Empty; - string? parameter = null; + ParameterExpression? basicParameter = null; + string? scriptedParameter = null; while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) { if (reader.TokenType == JsonTokenType.Comment) continue; @@ -423,7 +459,12 @@ internal static bool Load(ModuleRepository modules, Dictionary typeLo else if (reader.ValueTextEquals(ParameterProperty)) { reader.Read(); - parameter = reader.GetString() ?? string.Empty; + basicParameter = ParameterExpression.CreateParameter(reader.GetString() ?? string.Empty, typeof(string)); + } + else if (reader.ValueTextEquals(ParameterExpressionProperty)) + { + reader.Read(); + scriptedParameter = reader.GetString() ?? string.Empty; } else if (reader.ValueTextEquals(DisabledProperty)) { @@ -460,10 +501,14 @@ internal static bool Load(ModuleRepository modules, Dictionary typeLo { Location = point, Description = description, - ParameterValue = parameter, + ParameterValue = basicParameter, IsDisabled = disabled }; nodes.Add(index, mss); + if(scriptedParameter is not null) + { + scriptedParameters.Add((mss, scriptedParameter)); + } return true; } diff --git a/src/XTMF2/ModelSystemConstruct/ParameterExpression.cs b/src/XTMF2/ModelSystemConstruct/ParameterExpression.cs new file mode 100644 index 0000000..c220376 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/ParameterExpression.cs @@ -0,0 +1,93 @@ +/* + Copyright 2022 University of Toronto + This file is part of XTMF2. + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using XTMF2.ModelSystemConstruct.Parameters; + +namespace XTMF2.ModelSystemConstruct; + +/// +/// This class provides the interface for a Node to contain a value. +/// +public abstract class ParameterExpression : INotifyPropertyChanged +{ + /// + /// Checks to see if this parameter is capable of being converted into the given type. + /// + /// The type to be converted to. + /// The error message of why the operation failed, null if it succeeds. + /// True if the type can be converted, false with error message if not. + public abstract bool IsCompatible(Type type, [NotNullWhen(false)] ref string? errorString); + + /// + /// Tries to convert the parameter expression to the given type. + /// + /// The module that is requesting this parameter expression to be evaluated. + /// The type to try to extract. + /// An error message if the extraction fails. + /// An object of the given type, or null with an error message if it fails. + public abstract object? GetValue(IModule caller, Type type, ref string? errorString); + + /// + /// Gets a string based representation of the parameter + /// + public abstract string Representation { get; } + + /// + /// The type that this parameter expression will return. + /// + public abstract Type Type { get; } + + /// + /// Creates a parameter from a string value + /// + /// The string value of the parameter. + /// The type of the parameter + /// A new parameter using the value. + internal static ParameterExpression CreateParameter(string value, Type type) + { + return new BasicParameter(value, type); + } + + /// + /// Create a new parameter using the expression. + /// + /// + /// A new parameter using the expression. + internal static ParameterExpression CreateParameter(Expression expression) + { + return new ScriptedParameter(expression); + } + + /// + public event PropertyChangedEventHandler? PropertyChanged; + + /// + /// Update any GUI following this parameter letting it know the value changed. + /// + protected void InvokeRepresentationChanged() + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Representation))); + } + + /// + /// Write the given parameter expression out to the write stream. + /// + /// The writer to store the parameter to. + internal abstract void Save(Utf8JsonWriter writer); + +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/BasicParameter.cs b/src/XTMF2/ModelSystemConstruct/Parameters/BasicParameter.cs new file mode 100644 index 0000000..d83e49b --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/BasicParameter.cs @@ -0,0 +1,74 @@ +/* + Copyright 2022 University of Toronto + This file is part of XTMF2. + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; + +namespace XTMF2.ModelSystemConstruct.Parameters; + +/// +/// Provides the backing for a simple parameter +/// +internal class BasicParameter : ParameterExpression +{ + protected const string ParameterProperty = "Parameter"; + + /// + /// The string presentation of the parameter + /// + private string _value; + + private readonly Type _type; + + /// + /// Create a basic parameter + /// + /// The string value of the parameter. + public BasicParameter(string value, Type type) + { + _value = value; + _type = type; + } + + /// + public override string Representation + { + get => _value; + } + + /// + public override bool IsCompatible(Type type, [NotNullWhen(false)] ref string? errorString) + { + return ArbitraryParameterParser.Check(type, _value, ref errorString); + } + + public override object GetValue(IModule caller, Type type, ref string? errorString) + { + var (sucess, value) = ArbitraryParameterParser.ArbitraryParameterParse(type, _value, ref errorString); + if (sucess) + { + errorString = null; + return value!; + } + return false; + } + + public override Type Type => _type; + + internal override void Save(Utf8JsonWriter writer) + { + writer.WriteString(ParameterProperty, Representation); + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/AddOperator.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/AddOperator.cs new file mode 100644 index 0000000..0f277cb --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/AddOperator.cs @@ -0,0 +1,145 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// This provides the implementation for adding +/// +internal sealed class AddOperator : Expression +{ + /// + /// The left hand side expression + /// + private readonly Expression _lhs; + + /// + /// The right hand side expression. + /// + private readonly Expression _rhs; + + /// + /// Create a new add operation. + /// + /// The expression for the left hand side. + /// The expression for the right hand side. + /// The string representation of the add. + /// The offset into the full expression that this add starts at. + public AddOperator(Expression lhs, Expression rhs, ReadOnlyMemory expression, int offset) : base(expression, offset) + { + _lhs = lhs; + _rhs = rhs; + TestTypes(_lhs, _rhs, offset); + } + + /// + public override Type Type => _rhs.Type == typeof(string) ? typeof(string) : _lhs.Type; + + /// + internal override Result GetResult(IModule caller) + { + if (GetResult(caller, _lhs, out var lhs, out var errorResult) + && GetResult(caller, _rhs, out var rhs, out errorResult)) + { + if(CheckForStringConversion(lhs, rhs, out var convertedString)) + { + return convertedString; + } + else if (lhs is int l && rhs is int r) + { + return new IntegerResult(l + r); + } + else if(lhs is float lf && rhs is float rf) + { + return new FloatResult(lf + rf); + } + else + { + return new ErrorResult($"Unknown type pair for addition, {_lhs.Type.FullName} and {_rhs.Type.FullName}!", _lhs.Type); + } + } + return errorResult; + } + + /// + /// Check to see if this is a string conversion, and if so + /// + /// + /// + /// + /// + private static bool CheckForStringConversion(object lhs, object rhs, [NotNullWhen(true)] out Result? convertedString) + { + var l = lhs as string; + var r = rhs as string; + convertedString = null; + if(l is null && r is null) + { + return false; + } + convertedString = l is not null ? new StringResult(l + rhs!.ToString()) : new StringResult(lhs.ToString() + r); + return true; + } + + /// + /// Gets the result for the given expression. + /// + /// The module that has requested this expression to be evaluated. + /// The child expression to evaluate. + /// The result from the expression, 0 if false. + /// If false it will contain the error message from the expression. + /// Returns true if we were able to get the result of the expression, false otherwise with the error result. + private static bool GetResult(IModule caller, Expression child, [NotNullWhen(true)]out object? result, [NotNullWhen(false)] out Result? errorResult) + { + string? error = null; + errorResult = null; + var childResult = child.GetResult(caller); + if (childResult.TryGetResult(out result, ref error)) + { + return true; + } + result = default; + errorResult = childResult; + return false; + } + + private static Type[] _SupportedTypes = new[] { typeof(int), typeof(float), typeof(string) }; + + /// + /// Test to see if the types are compatible + /// + /// The LHS of the binary operator. + /// The RHS of the binary operator. + /// The offset into the full expression where this expression starts. + /// Throws a compiler exception of the types are not compatible. + private static void TestTypes(Expression lhs, Expression rhs, int offset) + { + var oneSideIsString = lhs.Type == typeof(string) || rhs.Type == typeof(string); + if (lhs.Type != rhs.Type && !oneSideIsString) + { + throw new CompilerException($"The LHS and RHS of the add operation are not of the same type unless adding with a string.! LHS = {lhs.Type.FullName}, RHS = {rhs.Type.FullName}", offset); + } + else if(!oneSideIsString && Array.IndexOf(_SupportedTypes, lhs.Type) < 0) + { + throw new CompilerException($"The + operator does not support the type {lhs.Type.FullName}!", offset); + } + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/AndOperator.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/AndOperator.cs new file mode 100644 index 0000000..a6b1372 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/AndOperator.cs @@ -0,0 +1,116 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// This provides the implementation for comparing +/// +internal sealed class AndOperator : Expression +{ + /// + /// The left hand side expression + /// + private readonly Expression _lhs; + + /// + /// The right hand side expression. + /// + private readonly Expression _rhs; + + /// + /// Create a new less than operation. + /// + /// The expression for the left hand side. + /// The expression for the right hand side. + /// The string representation of the add. + /// The offset into the full expression that this add starts at. + public AndOperator(Expression lhs, Expression rhs, ReadOnlyMemory expression, int offset) : base(expression, offset) + { + _lhs = lhs; + _rhs = rhs; + TestTypes(_lhs, _rhs, offset); + } + + /// + public override Type Type => typeof(bool); + + /// + internal override Result GetResult(IModule caller) + { + if (GetResult(caller, _lhs, out var lhs, out var errorResult) + && GetResult(caller, _rhs, out var rhs, out errorResult)) + { + if (lhs is bool lb && rhs is bool rb) + { + return new BooleanResult(lb && rb); + } + else + { + return new ErrorResult($"Unknown type pair for && operator, {_lhs.Type.FullName} and {_rhs.Type.FullName}!", _lhs.Type); + } + } + return errorResult; + } + + /// + /// Gets the result for the given expression. + /// + /// The module that has requested this expression to be evaluated. + /// The child expression to evaluate. + /// The result from the expression, 0 if false. + /// If false it will contain the error message from the expression. + /// Returns true if we were able to get the result of the expression, false otherwise with the error result. + private static bool GetResult(IModule caller, Expression child, out object? result, [NotNullWhen(false)] out Result? errorResult) + { + string? error = null; + errorResult = null; + var childResult = child.GetResult(caller); + if (childResult.TryGetResult(out result, ref error)) + { + return true; + } + result = default; + errorResult = childResult; + return false; + } + + private static Type[] _SupportedTypes = new[] { typeof(bool) }; + + /// + /// Test to see if the types are compatible + /// + /// The LHS of the binary operator. + /// The RHS of the binary operator. + /// The offset into the full expression where this expression starts. + /// Throws a compiler exception of the types are not compatible. + private static void TestTypes(Expression lhs, Expression rhs, int offset) + { + if (lhs.Type != rhs.Type) + { + throw new CompilerException($"The LHS and RHS of the && operator are not of the same type! LHS = {lhs.Type.FullName}, RHS = {rhs.Type.FullName}", offset); + } + else if (Array.IndexOf(_SupportedTypes, lhs.Type) < 0) + { + throw new CompilerException($"The && operator does not support the type {lhs.Type.FullName}!", offset); + } + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/DivideOperator.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/DivideOperator.cs new file mode 100644 index 0000000..b761472 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/DivideOperator.cs @@ -0,0 +1,120 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// This provides the implementation for dividing +/// +internal sealed class DivideOperator : Expression +{ + /// + /// The left hand side expression + /// + private readonly Expression _lhs; + + /// + /// The right hand side expression. + /// + private readonly Expression _rhs; + + /// + /// Create a new divide operation. + /// + /// The expression for the left hand side. + /// The expression for the right hand side. + /// The string representation of the add. + /// The offset into the full expression that this add starts at. + public DivideOperator(Expression lhs, Expression rhs, ReadOnlyMemory expression, int offset) : base(expression, offset) + { + _lhs = lhs; + _rhs = rhs; + TestTypes(_lhs, _rhs, offset); + } + + /// + public override Type Type => _lhs.Type; + + /// + internal override Result GetResult(IModule caller) + { + if (GetResult(caller, _lhs, out var lhs, out var errorResult) + && GetResult(caller, _rhs, out var rhs, out errorResult)) + { + if (lhs is int l && rhs is int r) + { + return new IntegerResult(l / r); + } + else if (lhs is float lf && rhs is float rf) + { + return new FloatResult(lf / rf); + } + else + { + return new ErrorResult($"Unknown type pair for multiplying, {_lhs.Type.FullName} and {_rhs.Type.FullName}!", _lhs.Type); + } + } + return errorResult; + } + + /// + /// Gets the result for the given expression. + /// + /// The module that has requested this expression to be evaluated. + /// The child expression to evaluate. + /// The result from the expression, 0 if false. + /// If false it will contain the error message from the expression. + /// Returns true if we were able to get the result of the expression, false otherwise with the error result. + private static bool GetResult(IModule caller, Expression child, out object? result, [NotNullWhen(false)] out Result? errorResult) + { + string? error = null; + errorResult = null; + var childResult = child.GetResult(caller); + if (childResult.TryGetResult(out result, ref error)) + { + return true; + } + result = default; + errorResult = childResult; + return false; + } + + private static Type[] _SupportedTypes = new[] { typeof(int), typeof(float) }; + + /// + /// Test to see if the types are compatible + /// + /// The LHS of the binary operator. + /// The RHS of the binary operator. + /// The offset into the full expression where this expression starts. + /// Throws a compiler exception of the types are not compatible. + private static void TestTypes(Expression lhs, Expression rhs, int offset) + { + if (lhs.Type != rhs.Type) + { + throw new CompilerException($"The LHS and RHS of the divide operation are not of the same type! LHS = {lhs.Type.FullName}, RHS = {rhs.Type.FullName}", offset); + } + else if (Array.IndexOf(_SupportedTypes, lhs.Type) < 0) + { + throw new CompilerException($"The / operator does not support the type {lhs.Type.FullName}!", offset); + } + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/EqualsOperator.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/EqualsOperator.cs new file mode 100644 index 0000000..cf6dd44 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/EqualsOperator.cs @@ -0,0 +1,124 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// This provides the implementation for comparing +/// +internal sealed class EqualsOperator : Expression +{ + /// + /// The left hand side expression + /// + private readonly Expression _lhs; + + /// + /// The right hand side expression. + /// + private readonly Expression _rhs; + + /// + /// Create a new less than operation. + /// + /// The expression for the left hand side. + /// The expression for the right hand side. + /// The string representation of the add. + /// The offset into the full expression that this add starts at. + public EqualsOperator(Expression lhs, Expression rhs, ReadOnlyMemory expression, int offset) : base(expression, offset) + { + _lhs = lhs; + _rhs = rhs; + TestTypes(_lhs, _rhs, offset); + } + + /// + public override Type Type => typeof(bool); + + /// + internal override Result GetResult(IModule caller) + { + if (GetResult(caller, _lhs, out var lhs, out var errorResult) + && GetResult(caller, _rhs, out var rhs, out errorResult)) + { + if (lhs is int l && rhs is int r) + { + return new BooleanResult(l == r); + } + else if (lhs is float lf && rhs is float rf) + { + return new BooleanResult(lf == rf); + } + else if (lhs is bool lb && rhs is bool rb) + { + return new BooleanResult(lb == rb); + } + else + { + return new ErrorResult($"Unknown type pair for == operator, {_lhs.Type.FullName} and {_rhs.Type.FullName}!", _lhs.Type); + } + } + return errorResult; + } + + /// + /// Gets the result for the given expression. + /// + /// The module that has requested this expression to be evaluated. + /// The child expression to evaluate. + /// The result from the expression, 0 if false. + /// If false it will contain the error message from the expression. + /// Returns true if we were able to get the result of the expression, false otherwise with the error result. + private static bool GetResult(IModule caller, Expression child, out object? result, [NotNullWhen(false)] out Result? errorResult) + { + string? error = null; + errorResult = null; + var childResult = child.GetResult(caller); + if (childResult.TryGetResult(out result, ref error)) + { + return true; + } + result = default; + errorResult = childResult; + return false; + } + + private static Type[] _SupportedTypes = new[] { typeof(int), typeof(float), typeof(bool) }; + + /// + /// Test to see if the types are compatible + /// + /// The LHS of the binary operator. + /// The RHS of the binary operator. + /// The offset into the full expression where this expression starts. + /// Throws a compiler exception of the types are not compatible. + private static void TestTypes(Expression lhs, Expression rhs, int offset) + { + if (lhs.Type != rhs.Type) + { + throw new CompilerException($"The LHS and RHS of the == operator are not of the same type! LHS = {lhs.Type.FullName}, RHS = {rhs.Type.FullName}", offset); + } + else if (Array.IndexOf(_SupportedTypes, lhs.Type) < 0) + { + throw new CompilerException($"The == operator does not support the type {lhs.Type.FullName}!", offset); + } + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/ExponentOperator.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/ExponentOperator.cs new file mode 100644 index 0000000..e231ee1 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/ExponentOperator.cs @@ -0,0 +1,120 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// This provides the implementation for exponents +/// +internal sealed class ExponentOperator : Expression +{ + /// + /// The left hand side expression + /// + private readonly Expression _lhs; + + /// + /// The right hand side expression. + /// + private readonly Expression _rhs; + + /// + /// Create a new exponent operation. + /// + /// The expression for the left hand side. + /// The expression for the right hand side. + /// The string representation of the exponent. + /// The offset into the full expression that this exponent starts at. + public ExponentOperator(Expression lhs, Expression rhs, ReadOnlyMemory expression, int offset) : base(expression, offset) + { + _lhs = lhs; + _rhs = rhs; + TestTypes(_lhs, _rhs, offset); + } + + /// + public override Type Type => _lhs.Type; + + /// + internal override Result GetResult(IModule caller) + { + if (GetResult(caller, _lhs, out var lhs, out var errorResult) + && GetResult(caller, _rhs, out var rhs, out errorResult)) + { + if (lhs is int l && rhs is int r) + { + return new IntegerResult((int)Math.Pow(l, r)); + } + else if(lhs is float lf && rhs is float rf) + { + return new FloatResult((float)Math.Pow(lf, rf)); + } + else + { + return new ErrorResult($"Unknown type pair for exponent, {_lhs.Type.FullName} and {_rhs.Type.FullName}!", _lhs.Type); + } + } + return errorResult; + } + + /// + /// Gets the result for the given expression. + /// + /// The module that has requested this expression to be evaluated. + /// The child expression to evaluate. + /// The result from the expression, 0 if false. + /// If false it will contain the error message from the expression. + /// Returns true if we were able to get the result of the expression, false otherwise with the error result. + private static bool GetResult(IModule caller, Expression child, out object? result, [NotNullWhen(false)] out Result? errorResult) + { + string? error = null; + errorResult = null; + var childResult = child.GetResult(caller); + if (childResult.TryGetResult(out result, ref error)) + { + return true; + } + result = default; + errorResult = childResult; + return false; + } + + private static Type[] _SupportedTypes = new[] { typeof(int), typeof(float) }; + + /// + /// Test to see if the types are compatible + /// + /// The LHS of the binary operator. + /// The RHS of the binary operator. + /// The offset into the full expression where this expression starts. + /// Throws a compiler exception of the types are not compatible. + private static void TestTypes(Expression lhs, Expression rhs, int offset) + { + if (lhs.Type != rhs.Type) + { + throw new CompilerException($"The LHS and RHS of the exponent operator are not of the same type! LHS = {lhs.Type.FullName}, RHS = {rhs.Type.FullName}", offset); + } + else if(Array.IndexOf(_SupportedTypes, lhs.Type) < 0) + { + throw new CompilerException($"The ^ operator does not support the type {lhs.Type.FullName}!", offset); + } + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/GreaterThan.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/GreaterThan.cs new file mode 100644 index 0000000..cb1b94d --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/GreaterThan.cs @@ -0,0 +1,120 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// This provides the implementation for comparing +/// +internal sealed class GreaterThanOperator : Expression +{ + /// + /// The left hand side expression + /// + private readonly Expression _lhs; + + /// + /// The right hand side expression. + /// + private readonly Expression _rhs; + + /// + /// Create a new less than operation. + /// + /// The expression for the left hand side. + /// The expression for the right hand side. + /// The string representation of the add. + /// The offset into the full expression that this add starts at. + public GreaterThanOperator(Expression lhs, Expression rhs, ReadOnlyMemory expression, int offset) : base(expression, offset) + { + _lhs = lhs; + _rhs = rhs; + TestTypes(_lhs, _rhs, offset); + } + + /// + public override Type Type => typeof(bool); + + /// + internal override Result GetResult(IModule caller) + { + if (GetResult(caller, _lhs, out var lhs, out var errorResult) + && GetResult(caller, _rhs, out var rhs, out errorResult)) + { + if (lhs is int l && rhs is int r) + { + return new BooleanResult(l > r); + } + else if (lhs is float lf && rhs is float rf) + { + return new BooleanResult(lf > rf); + } + else + { + return new ErrorResult($"Unknown type pair for > operator, {_lhs.Type.FullName} and {_rhs.Type.FullName}!", _lhs.Type); + } + } + return errorResult; + } + + /// + /// Gets the result for the given expression. + /// + /// The module that has requested this expression to be evaluated. + /// The child expression to evaluate. + /// The result from the expression, 0 if false. + /// If false it will contain the error message from the expression. + /// Returns true if we were able to get the result of the expression, false otherwise with the error result. + private static bool GetResult(IModule caller, Expression child, out object? result, [NotNullWhen(false)] out Result? errorResult) + { + string? error = null; + errorResult = null; + var childResult = child.GetResult(caller); + if (childResult.TryGetResult(out result, ref error)) + { + return true; + } + result = default; + errorResult = childResult; + return false; + } + + private static Type[] _SupportedTypes = new[] { typeof(int), typeof(float) }; + + /// + /// Test to see if the types are compatible + /// + /// The LHS of the binary operator. + /// The RHS of the binary operator. + /// The offset into the full expression where this expression starts. + /// Throws a compiler exception of the types are not compatible. + private static void TestTypes(Expression lhs, Expression rhs, int offset) + { + if (lhs.Type != rhs.Type) + { + throw new CompilerException($"The LHS and RHS of the > operation are not of the same type! LHS = {lhs.Type.FullName}, RHS = {rhs.Type.FullName}", offset); + } + else if (Array.IndexOf(_SupportedTypes, lhs.Type) < 0) + { + throw new CompilerException($"The > operator does not support the type {lhs.Type.FullName}!", offset); + } + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/GreaterThanOrEqual.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/GreaterThanOrEqual.cs new file mode 100644 index 0000000..8a85399 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/GreaterThanOrEqual.cs @@ -0,0 +1,120 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// This provides the implementation for comparing +/// +internal sealed class GreaterThanOrEqualOperator : Expression +{ + /// + /// The left hand side expression + /// + private readonly Expression _lhs; + + /// + /// The right hand side expression. + /// + private readonly Expression _rhs; + + /// + /// Create a new less than operation. + /// + /// The expression for the left hand side. + /// The expression for the right hand side. + /// The string representation of the add. + /// The offset into the full expression that this add starts at. + public GreaterThanOrEqualOperator(Expression lhs, Expression rhs, ReadOnlyMemory expression, int offset) : base(expression, offset) + { + _lhs = lhs; + _rhs = rhs; + TestTypes(_lhs, _rhs, offset); + } + + /// + public override Type Type => typeof(bool); + + /// + internal override Result GetResult(IModule caller) + { + if (GetResult(caller, _lhs, out var lhs, out var errorResult) + && GetResult(caller, _rhs, out var rhs, out errorResult)) + { + if (lhs is int l && rhs is int r) + { + return new BooleanResult(l >= r); + } + else if (lhs is float lf && rhs is float rf) + { + return new BooleanResult(lf >= rf); + } + else + { + return new ErrorResult($"Unknown type pair for >= operator, {_lhs.Type.FullName} and {_rhs.Type.FullName}!", _lhs.Type); + } + } + return errorResult; + } + + /// + /// Gets the result for the given expression. + /// + /// The module that has requested this expression to be evaluated. + /// The child expression to evaluate. + /// The result from the expression, 0 if false. + /// If false it will contain the error message from the expression. + /// Returns true if we were able to get the result of the expression, false otherwise with the error result. + private static bool GetResult(IModule caller, Expression child, out object? result, [NotNullWhen(false)] out Result? errorResult) + { + string? error = null; + errorResult = null; + var childResult = child.GetResult(caller); + if (childResult.TryGetResult(out result, ref error)) + { + return true; + } + result = default; + errorResult = childResult; + return false; + } + + private static Type[] _SupportedTypes = new[] { typeof(int), typeof(float) }; + + /// + /// Test to see if the types are compatible + /// + /// The LHS of the binary operator. + /// The RHS of the binary operator. + /// The offset into the full expression where this expression starts. + /// Throws a compiler exception of the types are not compatible. + private static void TestTypes(Expression lhs, Expression rhs, int offset) + { + if (lhs.Type != rhs.Type) + { + throw new CompilerException($"The LHS and RHS of the >= operation are not of the same type! LHS = {lhs.Type.FullName}, RHS = {rhs.Type.FullName}", offset); + } + else if (Array.IndexOf(_SupportedTypes, lhs.Type) < 0) + { + throw new CompilerException($"The >= operator does not support the type {lhs.Type.FullName}!", offset); + } + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/LessThanOperator.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/LessThanOperator.cs new file mode 100644 index 0000000..afdb596 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/LessThanOperator.cs @@ -0,0 +1,120 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// This provides the implementation for comparing +/// +internal sealed class LessThanOperator : Expression +{ + /// + /// The left hand side expression + /// + private readonly Expression _lhs; + + /// + /// The right hand side expression. + /// + private readonly Expression _rhs; + + /// + /// Create a new less than operation. + /// + /// The expression for the left hand side. + /// The expression for the right hand side. + /// The string representation of the add. + /// The offset into the full expression that this add starts at. + public LessThanOperator(Expression lhs, Expression rhs, ReadOnlyMemory expression, int offset) : base(expression, offset) + { + _lhs = lhs; + _rhs = rhs; + TestTypes(_lhs, _rhs, offset); + } + + /// + public override Type Type => typeof(bool); + + /// + internal override Result GetResult(IModule caller) + { + if (GetResult(caller, _lhs, out var lhs, out var errorResult) + && GetResult(caller, _rhs, out var rhs, out errorResult)) + { + if (lhs is int l && rhs is int r) + { + return new BooleanResult(l < r); + } + else if (lhs is float lf && rhs is float rf) + { + return new BooleanResult(lf < rf); + } + else + { + return new ErrorResult($"Unknown type pair for < operator, {_lhs.Type.FullName} and {_rhs.Type.FullName}!", _lhs.Type); + } + } + return errorResult; + } + + /// + /// Gets the result for the given expression. + /// + /// The module that has requested this expression to be evaluated. + /// The child expression to evaluate. + /// The result from the expression, 0 if false. + /// If false it will contain the error message from the expression. + /// Returns true if we were able to get the result of the expression, false otherwise with the error result. + private static bool GetResult(IModule caller, Expression child, out object? result, [NotNullWhen(false)] out Result? errorResult) + { + string? error = null; + errorResult = null; + var childResult = child.GetResult(caller); + if (childResult.TryGetResult(out result, ref error)) + { + return true; + } + result = default; + errorResult = childResult; + return false; + } + + private static Type[] _SupportedTypes = new[] { typeof(int), typeof(float) }; + + /// + /// Test to see if the types are compatible + /// + /// The LHS of the binary operator. + /// The RHS of the binary operator. + /// The offset into the full expression where this expression starts. + /// Throws a compiler exception of the types are not compatible. + private static void TestTypes(Expression lhs, Expression rhs, int offset) + { + if (lhs.Type != rhs.Type) + { + throw new CompilerException($"The LHS and RHS of the < operator are not of the same type! LHS = {lhs.Type.FullName}, RHS = {rhs.Type.FullName}", offset); + } + else if (Array.IndexOf(_SupportedTypes, lhs.Type) < 0) + { + throw new CompilerException($"The < operator does not support the type {lhs.Type.FullName}!", offset); + } + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/LessThanOrEqual.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/LessThanOrEqual.cs new file mode 100644 index 0000000..342ac87 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/LessThanOrEqual.cs @@ -0,0 +1,120 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// This provides the implementation for comparing +/// +internal sealed class LessThanOrEqualOperator : Expression +{ + /// + /// The left hand side expression + /// + private readonly Expression _lhs; + + /// + /// The right hand side expression. + /// + private readonly Expression _rhs; + + /// + /// Create a new less than operation. + /// + /// The expression for the left hand side. + /// The expression for the right hand side. + /// The string representation of the add. + /// The offset into the full expression that this add starts at. + public LessThanOrEqualOperator(Expression lhs, Expression rhs, ReadOnlyMemory expression, int offset) : base(expression, offset) + { + _lhs = lhs; + _rhs = rhs; + TestTypes(_lhs, _rhs, offset); + } + + /// + public override Type Type => typeof(bool); + + /// + internal override Result GetResult(IModule caller) + { + if (GetResult(caller, _lhs, out var lhs, out var errorResult) + && GetResult(caller, _rhs, out var rhs, out errorResult)) + { + if (lhs is int l && rhs is int r) + { + return new BooleanResult(l <= r); + } + else if (lhs is float lf && rhs is float rf) + { + return new BooleanResult(lf <= rf); + } + else + { + return new ErrorResult($"Unknown type pair for <= operator, {_lhs.Type.FullName} and {_rhs.Type.FullName}!", _lhs.Type); + } + } + return errorResult; + } + + /// + /// Gets the result for the given expression. + /// + /// The module that has requested this expression to be evaluated. + /// The child expression to evaluate. + /// The result from the expression, 0 if false. + /// If false it will contain the error message from the expression. + /// Returns true if we were able to get the result of the expression, false otherwise with the error result. + private static bool GetResult(IModule caller, Expression child, out object? result, [NotNullWhen(false)] out Result? errorResult) + { + string? error = null; + errorResult = null; + var childResult = child.GetResult(caller); + if (childResult.TryGetResult(out result, ref error)) + { + return true; + } + result = default; + errorResult = childResult; + return false; + } + + private static Type[] _SupportedTypes = new[] { typeof(int), typeof(float) }; + + /// + /// Test to see if the types are compatible + /// + /// The LHS of the binary operator. + /// The RHS of the binary operator. + /// The offset into the full expression where this expression starts. + /// Throws a compiler exception of the types are not compatible. + private static void TestTypes(Expression lhs, Expression rhs, int offset) + { + if (lhs.Type != rhs.Type) + { + throw new CompilerException($"The LHS and RHS of the <= operation are not of the same type! LHS = {lhs.Type.FullName}, RHS = {rhs.Type.FullName}", offset); + } + else if (Array.IndexOf(_SupportedTypes, lhs.Type) < 0) + { + throw new CompilerException($"The <= operator does not support the type {lhs.Type.FullName}!", offset); + } + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/MultiplyOperator.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/MultiplyOperator.cs new file mode 100644 index 0000000..03b3518 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/MultiplyOperator.cs @@ -0,0 +1,120 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// This provides the implementation for multiplying +/// +internal sealed class MultiplyOperator : Expression +{ + /// + /// The left hand side expression + /// + private readonly Expression _lhs; + + /// + /// The right hand side expression. + /// + private readonly Expression _rhs; + + /// + /// Create a new add operation. + /// + /// The expression for the left hand side. + /// The expression for the right hand side. + /// The string representation of the add. + /// The offset into the full expression that this add starts at. + public MultiplyOperator(Expression lhs, Expression rhs, ReadOnlyMemory expression, int offset) : base(expression, offset) + { + _lhs = lhs; + _rhs = rhs; + TestTypes(_lhs, _rhs, offset); + } + + /// + public override Type Type => _lhs.Type; + + /// + internal override Result GetResult(IModule caller) + { + if (GetResult(caller, _lhs, out var lhs, out var errorResult) + && GetResult(caller, _rhs, out var rhs, out errorResult)) + { + if (lhs is int l && rhs is int r) + { + return new IntegerResult(l * r); + } + else if (lhs is float lf && rhs is float rf) + { + return new FloatResult(lf * rf); + } + else + { + return new ErrorResult($"Unknown type pair for multiplying, {_lhs.Type.FullName} and {_rhs.Type.FullName}!", _lhs.Type); + } + } + return errorResult; + } + + /// + /// Gets the result for the given expression. + /// + /// The module that has requested this expression to be evaluated. + /// The child expression to evaluate. + /// The result from the expression, 0 if false. + /// If false it will contain the error message from the expression. + /// Returns true if we were able to get the result of the expression, false otherwise with the error result. + private static bool GetResult(IModule caller, Expression child, out object? result, [NotNullWhen(false)] out Result? errorResult) + { + string? error = null; + errorResult = null; + var childResult = child.GetResult(caller); + if (childResult.TryGetResult(out result, ref error)) + { + return true; + } + result = default; + errorResult = childResult; + return false; + } + + private static Type[] _SupportedTypes = new[] { typeof(int), typeof(float) }; + + /// + /// Test to see if the types are compatible + /// + /// The LHS of the binary operator. + /// The RHS of the binary operator. + /// The offset into the full expression where this expression starts. + /// Throws a compiler exception of the types are not compatible. + private static void TestTypes(Expression lhs, Expression rhs, int offset) + { + if (lhs.Type != rhs.Type) + { + throw new CompilerException($"The LHS and RHS of the * operator are not of the same type! LHS = {lhs.Type.FullName}, RHS = {rhs.Type.FullName}", offset); + } + else if (Array.IndexOf(_SupportedTypes, lhs.Type) < 0) + { + throw new CompilerException($"The * operator does not support the type {lhs.Type.FullName}!", offset); + } + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/NotEquals.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/NotEquals.cs new file mode 100644 index 0000000..a3f6e59 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/NotEquals.cs @@ -0,0 +1,124 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// This provides the implementation for comparing +/// +internal sealed class NotEqualsOperator : Expression +{ + /// + /// The left hand side expression + /// + private readonly Expression _lhs; + + /// + /// The right hand side expression. + /// + private readonly Expression _rhs; + + /// + /// Create a new less than operation. + /// + /// The expression for the left hand side. + /// The expression for the right hand side. + /// The string representation of the add. + /// The offset into the full expression that this add starts at. + public NotEqualsOperator(Expression lhs, Expression rhs, ReadOnlyMemory expression, int offset) : base(expression, offset) + { + _lhs = lhs; + _rhs = rhs; + TestTypes(_lhs, _rhs, offset); + } + + /// + public override Type Type => typeof(bool); + + /// + internal override Result GetResult(IModule caller) + { + if (GetResult(caller, _lhs, out var lhs, out var errorResult) + && GetResult(caller, _rhs, out var rhs, out errorResult)) + { + if (lhs is int l && rhs is int r) + { + return new BooleanResult(l != r); + } + else if (lhs is float lf && rhs is float rf) + { + return new BooleanResult(lf != rf); + } + else if(lhs is bool lb && rhs is bool rb) + { + return new BooleanResult(lb != rb); + } + else + { + return new ErrorResult($"Unknown type pair for != operator, {_lhs.Type.FullName} and {_rhs.Type.FullName}!", _lhs.Type); + } + } + return errorResult; + } + + /// + /// Gets the result for the given expression. + /// + /// The module that has requested this expression to be evaluated. + /// The child expression to evaluate. + /// The result from the expression, 0 if false. + /// If false it will contain the error message from the expression. + /// Returns true if we were able to get the result of the expression, false otherwise with the error result. + private static bool GetResult(IModule caller, Expression child, out object? result, [NotNullWhen(false)] out Result? errorResult) + { + string? error = null; + errorResult = null; + var childResult = child.GetResult(caller); + if (childResult.TryGetResult(out result, ref error)) + { + return true; + } + result = default; + errorResult = childResult; + return false; + } + + private static Type[] _SupportedTypes = new[] { typeof(int), typeof(float), typeof(bool) }; + + /// + /// Test to see if the types are compatible + /// + /// The LHS of the binary operator. + /// The RHS of the binary operator. + /// The offset into the full expression where this expression starts. + /// Throws a compiler exception of the types are not compatible. + private static void TestTypes(Expression lhs, Expression rhs, int offset) + { + if (lhs.Type != rhs.Type) + { + throw new CompilerException($"The LHS and RHS of the > operation are not of the same type! LHS = {lhs.Type.FullName}, RHS = {rhs.Type.FullName}", offset); + } + else if (Array.IndexOf(_SupportedTypes, lhs.Type) < 0) + { + throw new CompilerException($"The != operator does not support the type {lhs.Type.FullName}!", offset); + } + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/OrOperator.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/OrOperator.cs new file mode 100644 index 0000000..7213a85 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/OrOperator.cs @@ -0,0 +1,116 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// This provides the implementation for comparing +/// +internal sealed class OrOperator : Expression +{ + /// + /// The left hand side expression + /// + private readonly Expression _lhs; + + /// + /// The right hand side expression. + /// + private readonly Expression _rhs; + + /// + /// Create a new less than operation. + /// + /// The expression for the left hand side. + /// The expression for the right hand side. + /// The string representation of the add. + /// The offset into the full expression that this add starts at. + public OrOperator(Expression lhs, Expression rhs, ReadOnlyMemory expression, int offset) : base(expression, offset) + { + _lhs = lhs; + _rhs = rhs; + TestTypes(_lhs, _rhs, offset); + } + + /// + public override Type Type => typeof(bool); + + /// + internal override Result GetResult(IModule caller) + { + if (GetResult(caller, _lhs, out var lhs, out var errorResult) + && GetResult(caller, _rhs, out var rhs, out errorResult)) + { + if (lhs is bool lb && rhs is bool rb) + { + return new BooleanResult(lb || rb); + } + else + { + return new ErrorResult($"Unknown type pair for || operator, {_lhs.Type.FullName} and {_rhs.Type.FullName}!", _lhs.Type); + } + } + return errorResult; + } + + /// + /// Gets the result for the given expression. + /// + /// The module that has requested this expression to be evaluated. + /// The child expression to evaluate. + /// The result from the expression, 0 if false. + /// If false it will contain the error message from the expression. + /// Returns true if we were able to get the result of the expression, false otherwise with the error result. + private static bool GetResult(IModule caller, Expression child, out object? result, [NotNullWhen(false)] out Result? errorResult) + { + string? error = null; + errorResult = null; + var childResult = child.GetResult(caller); + if (childResult.TryGetResult(out result, ref error)) + { + return true; + } + result = default; + errorResult = childResult; + return false; + } + + private static Type[] _SupportedTypes = new[] { typeof(bool) }; + + /// + /// Test to see if the types are compatible + /// + /// The LHS of the binary operator. + /// The RHS of the binary operator. + /// The offset into the full expression where this expression starts. + /// Throws a compiler exception of the types are not compatible. + private static void TestTypes(Expression lhs, Expression rhs, int offset) + { + if (lhs.Type != rhs.Type) + { + throw new CompilerException($"The LHS and RHS of the || operator are not of the same type! LHS = {lhs.Type.FullName}, RHS = {rhs.Type.FullName}", offset); + } + else if (Array.IndexOf(_SupportedTypes, lhs.Type) < 0) + { + throw new CompilerException($"The || operator does not support the type {lhs.Type.FullName}!", offset); + } + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/SubtractOperator.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/SubtractOperator.cs new file mode 100644 index 0000000..10e93d5 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/SubtractOperator.cs @@ -0,0 +1,120 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// This provides the implementation for subtracting +/// +internal sealed class SubtractOperator : Expression +{ + /// + /// The left hand side expression + /// + private readonly Expression _lhs; + + /// + /// The right hand side expression. + /// + private readonly Expression _rhs; + + /// + /// Create a new add operation. + /// + /// The expression for the left hand side. + /// The expression for the right hand side. + /// The string representation of the add. + /// The offset into the full expression that this add starts at. + public SubtractOperator(Expression lhs, Expression rhs, ReadOnlyMemory expression, int offset) : base(expression, offset) + { + _lhs = lhs; + _rhs = rhs; + TestTypes(_lhs, _rhs, offset); + } + + /// + public override Type Type => _lhs.Type; + + /// + internal override Result GetResult(IModule caller) + { + if (GetResult(caller, _lhs, out var lhs, out var errorResult) + && GetResult(caller, _rhs, out var rhs, out errorResult)) + { + if (lhs is int l && rhs is int r) + { + return new IntegerResult(l - r); + } + else if (lhs is float lf && rhs is float rf) + { + return new FloatResult(lf - rf); + } + else + { + return new ErrorResult($"Unknown type pair for subtraction, {_lhs.Type.FullName} and {_rhs.Type.FullName}!", _lhs.Type); + } + } + return errorResult; + } + + /// + /// Gets the result for the given expression. + /// + /// The module that has requested this expression to be evaluated. + /// The child expression to evaluate. + /// The result from the expression, 0 if false. + /// If false it will contain the error message from the expression. + /// Returns true if we were able to get the result of the expression, false otherwise with the error result. + private static bool GetResult(IModule caller, Expression child, out object? result, [NotNullWhen(false)] out Result? errorResult) + { + string? error = null; + errorResult = null; + var childResult = child.GetResult(caller); + if (childResult.TryGetResult(out result, ref error)) + { + return true; + } + result = default; + errorResult = childResult; + return false; + } + + private static Type[] _SupportedTypes = new[] { typeof(int), typeof(float) }; + + /// + /// Test to see if the types are compatible + /// + /// The LHS of the binary operator. + /// The RHS of the binary operator. + /// The offset into the full expression where this expression starts. + /// Throws a compiler exception of the types are not compatible. + private static void TestTypes(Expression lhs, Expression rhs, int offset) + { + if (lhs.Type != rhs.Type) + { + throw new CompilerException($"The LHS and RHS of the - operator are not of the same type! LHS = {lhs.Type.FullName}, RHS = {rhs.Type.FullName}", offset); + } + else if (Array.IndexOf(_SupportedTypes, lhs.Type) < 0) + { + throw new CompilerException($"The - operator does not support the type {lhs.Type.FullName}!", offset); + } + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/CompilerException.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/CompilerException.cs new file mode 100644 index 0000000..318a280 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/CompilerException.cs @@ -0,0 +1,27 @@ +/* + Copyright 2022 University of Toronto + This file is part of XTMF2. + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +internal sealed class CompilerException : Exception +{ + public int Position { get; } + + public CompilerException(string? message, int position) : base(message) + { + Position = position; + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literal.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literal.cs new file mode 100644 index 0000000..c3055a0 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literal.cs @@ -0,0 +1,37 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ + +using System; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// Represents a constant value within an expression +/// +internal abstract class Literal : Expression +{ + /// + /// Create a new literal + /// + /// + /// The offset from the base of the full expression that we start at. + protected Literal(ReadOnlyMemory expression, int offset) : base(expression, offset) + { + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/BooleanLiteral.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/BooleanLiteral.cs new file mode 100644 index 0000000..c002074 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/BooleanLiteral.cs @@ -0,0 +1,50 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// Represents a boolean literal value +/// +internal sealed class BooleanLiteral : Literal +{ + /// + /// Create a new boolean literal. + /// + /// + /// + public BooleanLiteral(ReadOnlyMemory expression, int offset) : base(expression, offset) { } + + /// + internal override Result GetResult(IModule caller) + { + if(bool.TryParse(Text.Span, out bool result)) + { + return new BooleanResult(result); + } + else + { + return new ErrorResult($"Unable to process boolean literal, \"{Text}\" starting at position {Offset}!", Type); + } + } + + /// + public override Type Type => typeof(bool); +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/FloatLiteral.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/FloatLiteral.cs new file mode 100644 index 0000000..21bafce --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/FloatLiteral.cs @@ -0,0 +1,50 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// Represents a float literal value +/// +internal sealed class FloatLiteral : Literal +{ + /// + /// Create a new float literal. + /// + /// + /// + public FloatLiteral(ReadOnlyMemory expression, int offset) : base(expression, offset) { } + + /// + internal override Result GetResult(IModule caller) + { + if(float.TryParse(Text.Span, out var result)) + { + return new FloatResult(result); + } + else + { + return new ErrorResult($"Unable to process float literal, \"{Text}\" starting at position {Offset}!", Type); + } + } + + /// + public override Type Type => typeof(float); +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/IntegerLiteral.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/IntegerLiteral.cs new file mode 100644 index 0000000..e8ec957 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/IntegerLiteral.cs @@ -0,0 +1,50 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// Represents a integer literal value +/// +internal sealed class IntegerLiteral : Literal +{ + /// + /// Create a new integer literal + /// + /// + /// + public IntegerLiteral(ReadOnlyMemory expression, int offset) : base(expression, offset) { } + + /// + internal override Result GetResult(IModule caller) + { + if(int.TryParse(Text.Span, out var result)) + { + return new IntegerResult(result); + } + else + { + return new ErrorResult($"Unable to process integer literal, \"{Text}\" starting at position {Offset}!", Type); + } + } + + /// + public override Type Type => typeof(int); +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/StringLiteral.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/StringLiteral.cs new file mode 100644 index 0000000..72aae34 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/StringLiteral.cs @@ -0,0 +1,44 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// Represents a string literal value +/// +internal sealed class StringLiteral : Literal +{ + /// + /// Create a new string literal + /// + /// The expression containing the string literal. + /// The offset into the full expression that this starts at. + public StringLiteral(ReadOnlyMemory expression, int offset) : base(expression, offset) { } + + /// + public override Type Type => typeof(string); + + /// + internal override Result GetResult(IModule caller) + { + return new StringResult(new string(Text.Span)); + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/MonoOperators/NotOperator.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/MonoOperators/NotOperator.cs new file mode 100644 index 0000000..bd2b42c --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/MonoOperators/NotOperator.cs @@ -0,0 +1,60 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// Provides the ability to invert a boolean result. +/// +internal class NotOperator : Expression +{ + + private readonly Expression _innerExpression; + + public NotOperator(Expression innerExpression, ReadOnlyMemory text, int offset) : base(text, offset) + { + _innerExpression = innerExpression; + } + + /// + public override Type Type => typeof(bool); + + /// + internal override Result GetResult(IModule caller) + { + string? error = null; + var result = _innerExpression.GetResult(caller); + if(result.TryGetResult(out var value, ref error)) + { + if(value is bool b) + { + return new BooleanResult(!b); + } + } + if (result is ErrorResult) + { + return result; + } + else + { + return new ErrorResult("Invalid result type passed into not operator!", typeof(bool)); + } + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/ParameterCompiler.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/ParameterCompiler.cs new file mode 100644 index 0000000..08f5e50 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/ParameterCompiler.cs @@ -0,0 +1,670 @@ +/* + Copyright 2022 University of Toronto + This file is part of XTMF2. + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// Provides the entry point for compiling and evaluating expressions. +/// +public static class ParameterCompiler +{ + /// + /// Evaluate the expression and return the value. + /// + /// The module that is requesting this expression to be evaluated. + /// The expression to evaluate. + /// The returned object from the evaluation, null if the expression can not be evaluated.* + /// An error message if Evaluate returns false. + /// True if the expression was evaluated correctly. + public static bool Evaluate(IModule module, Expression expression, [NotNullWhen(true)] out object? value, [NotNullWhen(false)] ref string? error) + { + var result = expression.GetResult(module); + if (result.TryGetResult(out value, ref error)) + { + return true; + } + throw new XTMFRuntimeException(module, error); + } + + /// + /// + /// + /// + /// + /// + /// + /// + public static bool CreateExpression(IList nodes, string expresionText, [NotNullWhen(true)] out Expression? expression, [NotNullWhen(false)] ref string? error) + { + error = null; + try + { + ScanForInvalidBrackets(expresionText); + return Compile(nodes, expresionText.AsMemory(), 0, out expression); + } + catch (CompilerException exception) + { + error = exception.Message + $" Position: {exception.Position}"; + expression = null; + return false; + } + } + + private static bool Compile(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + if ( + GetSelect(nodes, text, offset, out expression) + || GetOr(nodes, text, offset, out expression) + || GetAnd(nodes, text, offset, out expression) + || GetEquals(nodes, text, offset, out expression) + || GetNotEquals(nodes, text, offset, out expression) + || GetGreaterThanOrEqual(nodes, text, offset, out expression) + || GetGreaterThan(nodes, text, offset, out expression) + || GetLessThanOrEqual(nodes, text, offset, out expression) + || GetLessThan(nodes, text, offset, out expression) + || GetAdd(nodes, text, offset, out expression) + || GetSubtract(nodes, text, offset, out expression) + || GetMultiply(nodes, text, offset, out expression) + || GetDivide(nodes, text, offset, out expression) + || GetExponent(nodes, text, offset, out expression) + || GetNot(nodes, text, offset, out expression) + || GetBracket(nodes, text, offset, out expression) + || GetVariable(nodes, text, offset, out expression) + || GetStringLiteral(text, offset, out expression) + || GetBooleanLiteral(text, offset, out expression) + || GetIntegerLiteral(text, offset, out expression) + || GetFloatingPointLiteral(text, offset, out expression) + ) + { + return true; + } + UnableToInterpret(text, offset); + // This will never actually be executed + return false; + } + + private static bool GetSelect(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + expression = null; + var span = text.Span; + var startOfCondition = IndexOfFirstNonWhiteSpace(span); + if (startOfCondition < 0) + { + return false; + } + int operatorIndex = IndexOfFirstOutsideOfBrackets(span, startOfCondition, '?'); + // if there was no select we can exit + if (operatorIndex <= -1) + { + return false; + } + int breakIndex = -1; + // We start at 1 so that when we decrement this to zero we have our break. + int breakCount = 1; + bool insideString = false; + for (int i = operatorIndex + 1; i < span.Length; i++) + { + switch (span[i]) + { + case '\"': + insideString = !insideString; + break; + case '?': + if (!insideString) + { + breakCount++; + } + break; + case ':': + if (!insideString) + { + breakCount--; + if (breakCount == 0) + { + breakIndex = i; + } + if (breakCount < 0) + { + throw new CompilerException("There are an extra select break operator (:)!", offset + i); + } + } + break; + } + } + if (breakIndex < 0) + { + throw new CompilerException("There is no matching select break operator (:) for select operator (?)!", operatorIndex + offset); + } + + if (!Compile(nodes, text.Slice(startOfCondition, operatorIndex - startOfCondition), offset + startOfCondition, out var condition) + || !Compile(nodes, text.Slice(operatorIndex + 1, breakIndex - (operatorIndex + 1)), offset + operatorIndex + 1, out var lhs) + || !Compile(nodes, text[(breakIndex + 1)..], offset + breakIndex + 1, out var rhs)) + { + return false; + } + expression = new SelectOperator(condition, lhs, rhs, text[startOfCondition..], startOfCondition + offset); + return true; + } + + private static bool GetAnd(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + expression = GetBinaryOperator(nodes, text, offset, "&&", out var startOfLHS, out var lhs, out var rhs) ? + new AndOperator(lhs, rhs, text[startOfLHS..], startOfLHS + offset) : null; + return expression is not null; + } + + private static bool GetOr(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + expression = GetBinaryOperator(nodes, text, offset, "||", out var startOfLHS, out var lhs, out var rhs) ? + new OrOperator(lhs, rhs, text[startOfLHS..], startOfLHS + offset) : null; + return expression is not null; + } + + private static bool GetAdd(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + expression = GetBinaryOperator(nodes, text, offset, '+', out var startOfLHS, out var lhs, out var rhs) ? + new AddOperator(lhs, rhs, text[startOfLHS..], startOfLHS + offset) : null; + return expression is not null; + } + + private static bool GetSubtract(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + expression = GetBinaryOperator(nodes, text, offset, '-', out var startOfLHS, out var lhs, out var rhs) ? + new SubtractOperator(lhs, rhs, text[startOfLHS..], startOfLHS + offset) : null; + return expression is not null; + } + + private static bool GetMultiply(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + expression = GetBinaryOperator(nodes, text, offset, '*', out var startOfLHS, out var lhs, out var rhs) ? + new MultiplyOperator(lhs, rhs, text[startOfLHS..], startOfLHS + offset) : null; + return expression is not null; + } + + private static bool GetDivide(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + expression = GetBinaryOperator(nodes, text, offset, '/', out var startOfLHS, out var lhs, out var rhs) ? + new DivideOperator(lhs, rhs, text[startOfLHS..], startOfLHS + offset) : null; + return expression is not null; + } + + private static bool GetExponent(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + expression = GetBinaryOperator(nodes, text, offset, '^', out var startOfLHS, out var lhs, out var rhs) ? + new ExponentOperator(lhs, rhs, text[startOfLHS..], startOfLHS + offset) : null; + return expression is not null; + } + + private static bool GetLessThan(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + expression = GetBinaryOperator(nodes, text, offset, '<', out var startOfLHS, out var lhs, out var rhs) ? + new LessThanOperator(lhs, rhs, text[startOfLHS..], startOfLHS + offset) : null; + return expression is not null; + } + + private static bool GetLessThanOrEqual(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + expression = GetBinaryOperator(nodes, text, offset, "<=", out var startOfLHS, out var lhs, out var rhs) ? + new LessThanOrEqualOperator(lhs, rhs, text[startOfLHS..], startOfLHS + offset) : null; + return expression is not null; + } + + private static bool GetGreaterThan(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + expression = GetBinaryOperator(nodes, text, offset, '>', out var startOfLHS, out var lhs, out var rhs) ? + new GreaterThanOperator(lhs, rhs, text[startOfLHS..], startOfLHS + offset) : null; + return expression is not null; + } + + private static bool GetGreaterThanOrEqual(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + expression = GetBinaryOperator(nodes, text, offset, ">=", out var startOfLHS, out var lhs, out var rhs) ? + new GreaterThanOrEqualOperator(lhs, rhs, text[startOfLHS..], startOfLHS + offset) : null; + return expression is not null; + } + + private static bool GetEquals(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + expression = GetBinaryOperator(nodes, text, offset, "==", out var startOfLHS, out var lhs, out var rhs) ? + new EqualsOperator(lhs, rhs, text[startOfLHS..], startOfLHS + offset) : null; + return expression is not null; + } + + private static bool GetNotEquals(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + expression = GetBinaryOperator(nodes, text, offset, "!=", out var startOfLHS, out var lhs, out var rhs) ? + new NotEqualsOperator(lhs, rhs, text[startOfLHS..], startOfLHS + offset) : null; + return expression is not null; + } + + private static bool GetBinaryOperator(IList nodes, ReadOnlyMemory text, int offset, char operatorCharacter, out int startOfLHS, [NotNullWhen(true)] out Expression? lhs, [NotNullWhen(true)] out Expression? rhs) + { + lhs = null; + rhs = null; + var span = text.Span; + int operatorIndex; + startOfLHS = IndexOfFirstNonWhiteSpace(span); + return (startOfLHS >= 0 + && (operatorIndex = IndexOfOutsideOfBrackets(span, startOfLHS, operatorCharacter)) >= 0 + && operatorIndex > startOfLHS + && Compile(nodes, text.Slice(startOfLHS, operatorIndex - startOfLHS), offset + startOfLHS, out lhs) + && Compile(nodes, text[(operatorIndex + 1)..], offset + operatorIndex + 1, out rhs)); + } + + private static bool GetBinaryOperator(IList nodes, ReadOnlyMemory text, int offset, string operatorCharacter, out int startOfLHS, [NotNullWhen(true)] out Expression? lhs, [NotNullWhen(true)] out Expression? rhs) + { + lhs = null; + rhs = null; + var span = text.Span; + int operatorIndex; + startOfLHS = IndexOfFirstNonWhiteSpace(span); + return (startOfLHS >= 0 + && (operatorIndex = IndexOfOutsideOfBrackets(span, startOfLHS, operatorCharacter)) >= 0 + && operatorIndex > startOfLHS + && Compile(nodes, text.Slice(startOfLHS, operatorIndex - startOfLHS), offset + startOfLHS, out lhs) + && Compile(nodes, text[(operatorIndex + operatorCharacter.Length)..], offset + operatorIndex + operatorCharacter.Length, out rhs)); + } + + private static bool GetNot(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + expression = null; + var span = text.Span; + var first = IndexOfFirstNonWhiteSpace(span); + if (first == -1 + || span[first] != '!' + || !Compile(nodes, text[(first + 1)..], offset + first + 1, out var inner)) + { + return false; + } + if (inner.Type != typeof(bool)) + { + throw new CompilerException($"Invalid expression type {inner.Type.FullName} passed into Not Operator!", first + offset); + } + expression = new NotOperator(inner, text[first..], offset + first); + return true; + } + + private static bool GetBracket(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + var span = text.Span; + int bracketCount = 0; + int first = -1; + int second = -1; + expression = null; + for (int i = 0; i < span.Length; i++) + { + if (span[i] == '(') + { + if (first < 0) + { + first = i; + } + bracketCount++; + } + else if (span[i] == ')') + { + bracketCount--; + if (bracketCount == 0) + { + second = i; + break; + } + } + // If we come across a non-bracket non-white-space character before our bracket opens + else if (first < 0 && !char.IsWhiteSpace(span[i])) + { + return false; + } + } + if (first == -1 + || IndexOfFirstNonWhiteSpace(span[(second + 1)..]) >= 0) + { + return false; + } + // Optimize out the bracket + return Compile(nodes, text.Slice(first + 1, second - first - 1), offset + first + 1, out expression); + } + + private static bool GetVariable(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + expression = null; + var span = text.Span; + var start = IndexOfFirstNonWhiteSpace(span); + if (start < 0) + { + return false; + } + var end = start; + for (; end < span.Length; end++) + { + if (char.IsWhiteSpace(span[end])) + { + break; + } + } + // If there was nothing here or if we find any non-white space characters after the end of our variable name + if (end == start + || (end < span.Length && IndexOfFirstNonWhiteSpace(span[end..]) >= 0)) + { + return false; + } + var innerText = text[start..end]; + var node = nodes.FirstOrDefault(n => innerText.Span.Equals(n.Name.AsSpan(), StringComparison.InvariantCulture)); + if (node is not null) + { + expression = Variable.CreateVariableForNode(node, innerText, offset + start); + return true; + } + return false; + } + + private static bool GetStringLiteral(ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + var span = text.Span; + expression = null; + int first = span.IndexOf('"'); + if (first == -1 || first != IndexOfFirstNonWhiteSpace(span)) + { + return false; + } + int second = span[(first + 1)..].IndexOf('"'); + // If there is anything after the second quote this is an invalid string literal. + // +2 skips the ending quote + if (IndexOfFirstNonWhiteSpace(span[(second + first + 2)..]) >= 0) + { + return false; + } + expression = new StringLiteral(text.Slice(first + 1, second), offset + first); + return true; + } + + private static bool GetBooleanLiteral(ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + expression = null; + var span = text.Span; + var start = IndexOfFirstNonWhiteSpace(span); + if (start < 0) + { + return false; + } + var end = start; + for (; end < span.Length; end++) + { + if (char.IsWhiteSpace(span[end])) + { + break; + } + } + // If there was nothing here or if we find any non-white space characters after the end of our number + if (end == start + || (end < span.Length && IndexOfFirstNonWhiteSpace(span[end..]) >= 0)) + { + return false; + } + var innerText = span[start..end]; + if (bool.TryParse(innerText, out _)) + { + expression = new BooleanLiteral(text[start..end], offset + start); + return true; + } + return false; + } + + private static bool GetIntegerLiteral(ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + expression = null; + var span = text.Span; + var start = IndexOfFirstNonWhiteSpace(span); + if (start < 0) + { + return false; + } + var end = start; + for (; end < span.Length; end++) + { + if (!char.IsDigit(span[end])) + { + break; + } + } + // If there was nothing here or if we find any non-white space characters after the end of our number + if (end == start + || (end < span.Length && IndexOfFirstNonWhiteSpace(span.Slice(end)) >= 0)) + { + return false; + } + expression = new IntegerLiteral(text[start..end], offset + start); + return true; + } + + private static bool GetFloatingPointLiteral(ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + expression = null; + var span = text.Span; + var start = IndexOfFirstNonWhiteSpace(span); + if (start < 0) + { + return false; + } + var end = start; + for (; end < span.Length; end++) + { + if (!(char.IsDigit(span[end]) || span[end] == '.')) + { + break; + } + } + // If there was nothing here or if we find any non-white space characters after the end of our number + if (end == start + || (end < span.Length && IndexOfFirstNonWhiteSpace(span.Slice(end)) >= 0)) + { + return false; + } + expression = new FloatLiteral(text[start..end], offset + start); + return true; + } + + /// + /// Gets the first index of a non-white space character. + /// + /// The text to search* + /// The index of the first non-white space character, or -1 if there are none. + private static int IndexOfFirstNonWhiteSpace(ReadOnlySpan text) + { + for (int i = 0; i < text.Length; i++) + { + if (!char.IsWhiteSpace(text[i])) + { + return i; + } + } + return -1; + } + + /// + /// Gets the first index starting at the given position that is not inside of a bracket where the character is found. + /// If the character does not exist, this will return negative one. + /// + /// The text to search. + /// The position in the text to start searching. + /// The character that we wish to find. + /// The position the character is found, outside of brackets, or -1 if it is not found. + private static int IndexOfFirstOutsideOfBrackets(ReadOnlySpan text, int start, char characterToFind) + { + int bracketCounter = 0; + bool insideString = false; + for (int i = start; i < text.Length; i++) + { + if (text[i] == '\"') + { + insideString = !insideString; + } + else if (!insideString && text[i] == '(') + { + bracketCounter++; + } + else if (!insideString && text[i] == ')') + { + bracketCounter--; + } + else if (!insideString && text[i] == characterToFind && bracketCounter == 0) + { + return i; + } + } + return -1; + } + + /// + /// Gets the last index starting at the given position that is not inside of a bracket where the character is found. + /// If the character does not exist, this will return negative one. + /// + /// The text to search. + /// The position in the text to start searching. + /// The character that we wish to find. + /// The position the character is found, outside of brackets, or -1 if it is not found. + private static int IndexOfOutsideOfBrackets(ReadOnlySpan text, int start, char characterToFind) + { + int bracketCounter = 0; + int lastFound = -1; + bool insideString = false; + for (int i = start; i < text.Length; i++) + { + if (text[i] == '\"') + { + insideString = !insideString; + } + else if (!insideString && text[i] == '(') + { + bracketCounter++; + } + else if (!insideString && text[i] == ')') + { + bracketCounter--; + } + else if (!insideString && text[i] == characterToFind && bracketCounter == 0) + { + lastFound = i; + } + } + return lastFound; + } + + /// + /// Gets the last index starting at the given position that is not inside of a bracket where the character is found. + /// If the string does not exist, this will return negative one. + /// + /// The text to search. + /// The position in the text to start searching. + /// The character that we wish to find. + /// The position the string is found, outside of brackets, or -1 if it is not found. + private static int IndexOfOutsideOfBrackets(ReadOnlySpan text, int start, string stringToFind) + { + int bracketCounter = 0; + int lastFound = -1; + var firstCharacter = stringToFind[0]; + bool insideString = false; + for (int i = start; i < text.Length - stringToFind.Length; i++) + { + if (text[i] == '\"') + { + insideString = !insideString; + } + else if (!insideString && text[i] == '(') + { + bracketCounter++; + } + else if (!insideString && text[i] == ')') + { + bracketCounter--; + } + else if (!insideString && bracketCounter == 0 && text[i] == firstCharacter) + { + bool allMatch = true; + for (int j = 0; j < stringToFind.Length; j++) + { + if (text[i + j] != stringToFind[j]) + { + allMatch = false; + } + } + if (allMatch) + { + lastFound = i; + } + } + } + return lastFound; + } + + /// + /// Detects if we have an invalid set of brackets at the start of the compilation phase. + /// + /// The full text of the expression. + /// Throws an exception if a miss-matched set of brackets or quotes are found. + private static void ScanForInvalidBrackets(string expresionText) + { + int bracketCount = 0; + int mostOutsideBracketIndex = -1; + int lastStringStart = -1; + var span = expresionText.AsSpan(); + bool insideString = false; + for (int i = 0; i < span.Length; i++) + { + if (span[i] == '\"') + { + insideString = !insideString; + if (insideString) + { + lastStringStart = i; + } + } + else if (!insideString && span[i] == '(') + { + if (bracketCount == 0) + { + mostOutsideBracketIndex = i; + } + bracketCount++; + } + else if (!insideString && span[i] == ')') + { + bracketCount--; + if (bracketCount < 0) + { + throw new CompilerException("Invalid closing bracket found!", i); + } + } + } + if (bracketCount > 0) + { + throw new CompilerException("Unmatched bracket found!", mostOutsideBracketIndex); + } + if (insideString) + { + throw new CompilerException("Unmatched quote for string literal!", lastStringStart); + } + } + + [DoesNotReturn] + private static void UnableToInterpret(ReadOnlyMemory text, int offset) + { + throw new CompilerException($"Unable to interpret \"{text}\"", offset); + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Result.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Result.cs new file mode 100644 index 0000000..17c9c71 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Result.cs @@ -0,0 +1,42 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ + +using System; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// Contains the result of the expression. +/// +public abstract class Result +{ + /// + /// The type that would be returned. + /// + public abstract Type ReturnType { get; } + + /// + /// Gets the result of the computation. + /// + /// The result contained unless there was an error. + /// An error message if there was an error, null otherwise. + /// True if the expression succeeds, false otherwise with error message. + public abstract bool TryGetResult([NotNullWhen(true)] out object? result, [NotNullWhen(false)] ref string? error); +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Results/BooleanResult.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Results/BooleanResult.cs new file mode 100644 index 0000000..acad411 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Results/BooleanResult.cs @@ -0,0 +1,52 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// Implements the result that evaluates to a Boolean value. +/// +internal sealed class BooleanResult : Result +{ + /// + public override Type ReturnType => typeof(bool); + + /// + /// The contained result + /// + private readonly bool _value; + + /// + /// Create a new result. + /// + /// The value to return + public BooleanResult(bool result) + { + _value = result; + } + + /// + public override bool TryGetResult([NotNullWhen(true)] out object? result, [NotNullWhen(false)] ref string? error) + { + result = _value; + return true; + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Results/ErrorResult.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Results/ErrorResult.cs new file mode 100644 index 0000000..798f0ef --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Results/ErrorResult.cs @@ -0,0 +1,59 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// Implements the result that evaluates to a Boolean value. +/// +internal sealed class ErrorResult : Result +{ + /// + public override Type ReturnType => _returnType; + + /// + /// The contained error message + /// + private readonly string _errorMessage; + + /// + /// The type this error returns. + /// + private readonly Type _returnType; + + /// + /// Create a new result. + /// + /// The value to return + public ErrorResult(string errorMessage, Type returnType) + { + _errorMessage = errorMessage; + _returnType = returnType; + } + + /// + public override bool TryGetResult([NotNullWhen(true)] out object? result, [NotNullWhen(false)] ref string? error) + { + result = null; + error = _errorMessage; + return false; + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Results/FloatResult.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Results/FloatResult.cs new file mode 100644 index 0000000..e51395e --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Results/FloatResult.cs @@ -0,0 +1,52 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// Implements the result that evaluates to a Boolean value. +/// +internal sealed class FloatResult : Result +{ + /// + public override Type ReturnType => typeof(float); + + /// + /// The contained result + /// + private readonly float _value; + + /// + /// Create a new result. + /// + /// The value to return + public FloatResult(float result) + { + _value = result; + } + + /// + public override bool TryGetResult([NotNullWhen(true)] out object? result, [NotNullWhen(false)] ref string? error) + { + result = _value; + return true; + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Results/IntegerResult.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Results/IntegerResult.cs new file mode 100644 index 0000000..455bcac --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Results/IntegerResult.cs @@ -0,0 +1,52 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// Implements the result that evaluates to a Boolean value. +/// +internal sealed class IntegerResult : Result +{ + /// + public override Type ReturnType => typeof(int); + + /// + /// The contained result + /// + private readonly int _value; + + /// + /// Create a new result. + /// + /// The value to return + public IntegerResult(int result) + { + _value = result; + } + + /// + public override bool TryGetResult([NotNullWhen(true)] out object? result, [NotNullWhen(false)] ref string? error) + { + result = _value; + return true; + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Results/StringResult.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Results/StringResult.cs new file mode 100644 index 0000000..c2d7da6 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Results/StringResult.cs @@ -0,0 +1,52 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// Implements the result that evaluates to a Boolean value. +/// +internal sealed class StringResult : Result +{ + /// + public override Type ReturnType => typeof(string); + + /// + /// The contained result + /// + private readonly string _value; + + /// + /// Create a new result. + /// + /// The value to return + public StringResult(string result) + { + _value = result; + } + + /// + public override bool TryGetResult([NotNullWhen(true)] out object? result, [NotNullWhen(false)] ref string? error) + { + result = _value; + return true; + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/SelectOperator.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/SelectOperator.cs new file mode 100644 index 0000000..45c9192 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/SelectOperator.cs @@ -0,0 +1,119 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +internal sealed class SelectOperator : Expression +{ + /// + /// The left hand side expression + /// + private readonly Expression _condition; + + /// + /// The left hand side expression + /// + private readonly Expression _lhs; + + /// + /// The right hand side expression. + /// + private readonly Expression _rhs; + + /// + /// Create a new select operation. + /// + /// The expression for the left hand side. + /// The expression for the right hand side. + /// The string representation of the add. + /// The offset into the full expression that this add starts at. + public SelectOperator(Expression condition, Expression lhs, Expression rhs, ReadOnlyMemory expression, int offset) : base(expression, offset) + { + _condition = condition; + _lhs = lhs; + _rhs = rhs; + TestTypes(_condition, _lhs, _rhs, offset); + } + + /// + public override Type Type => _lhs.Type; + + /// + internal override Result GetResult(IModule caller) + { + if(!GetResult(caller, _condition, out var condition, out var errorResult)) + { + return errorResult; + } + if(condition is not bool cond) + { + return new ErrorResult("The condition of the expression was not a boolean!", _lhs.Type); + } + return (cond ? _lhs : _rhs).GetResult(caller); + } + + /// + /// Gets the result for the given expression. + /// + /// The module that has requested this expression to be evaluated. + /// The child expression to evaluate. + /// The result from the expression, 0 if false. + /// If false it will contain the error message from the expression. + /// Returns true if we were able to get the result of the expression, false otherwise with the error result. + private static bool GetResult(IModule caller, Expression child, out object? result, [NotNullWhen(false)] out Result? errorResult) + { + string? error = null; + errorResult = null; + var childResult = child.GetResult(caller); + if (childResult.TryGetResult(out result, ref error)) + { + return true; + } + result = default; + errorResult = childResult; + return false; + } + + private static Type[] _SupportedTypes = new[] { typeof(int), typeof(float), typeof(string) }; + + /// + /// Test to see if the types are compatible + /// + /// The LHS of the binary operator. + /// The RHS of the binary operator. + /// The offset into the full expression where this expression starts. + /// Throws a compiler exception of the types are not compatible. + private static void TestTypes(Expression condition, Expression lhs, Expression rhs, int offset) + { + if(condition.Type != typeof(bool)) + { + throw new CompilerException($"The condition of the select (?:) operator must be a boolean!", offset); + } + if (lhs.Type != rhs.Type) + { + throw new CompilerException($"The True and False sections of select operator are not of the same type! LHS = {lhs.Type.FullName}, RHS = {rhs.Type.FullName}", offset); + } + else if (Array.IndexOf(_SupportedTypes, lhs.Type) < 0) + { + throw new CompilerException($"The select (?:) operator does not support the type {lhs.Type.FullName}!", offset); + } + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Variable.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Variable.cs new file mode 100644 index 0000000..d8b0b71 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Variable.cs @@ -0,0 +1,47 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +internal abstract class Variable : Expression +{ + public Variable(ReadOnlyMemory text, int offset) : base(text, offset) + { + + } + + internal static Variable CreateVariableForNode(Node node, ReadOnlyMemory text, int offset) + { + var parameterValue = node.ParameterValue; + if(parameterValue is null) + { + throw new CompilerException($"Unable to create a variable for node {node.Name} because it has no parameter value!", offset); + } + return parameterValue.Type.FullName switch + { + "System.Boolean" => new BooleanVariable(text, offset, node), + "System.Int32" => new IntegerVariable(text, offset, node), + "System.Single" => new FloatVariable(text, offset, node), + "System.String" => new StringVariable(text, offset, node), + _ => throw new CompilerException($"Invalid type for a variable {parameterValue.Type.FullName} found when trying to" + + $" use {node.Name}!", offset) + }; + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Variables/BooleanVariable.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Variables/BooleanVariable.cs new file mode 100644 index 0000000..d54eab5 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Variables/BooleanVariable.cs @@ -0,0 +1,56 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// This class represents a boolean value that can change during +/// a model system run. +/// +internal sealed class BooleanVariable : Variable +{ + /// + /// The node that backs this variable + /// + private readonly Node _backingNode; + + public BooleanVariable(ReadOnlyMemory text, int offset, Node backingNode) : base(text, offset) + { + _backingNode = backingNode; + } + + public override Type Type => typeof(bool); + + internal override Result GetResult(IModule caller) + { + string? error = null; + var expression = _backingNode.ParameterValue; + if (expression is null || expression?.IsCompatible(typeof(bool), ref error) != true) + { + return new ErrorResult(error ?? $"{_backingNode.Name} does not have an expression!", typeof(bool)); + } + var ret = expression.GetValue(caller, typeof(bool), ref error); + if (ret is bool b) + { + return new BooleanResult(b); + } + return new ErrorResult(error!, typeof(bool)); + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Variables/FloatVariable.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Variables/FloatVariable.cs new file mode 100644 index 0000000..fae857c --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Variables/FloatVariable.cs @@ -0,0 +1,56 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// This class represents a boolean value that can change during +/// a model system run. +/// +internal sealed class FloatVariable : Variable +{ + /// + /// The node that backs this variable + /// + private readonly Node _backingNode; + + public FloatVariable(ReadOnlyMemory text, int offset, Node backingNode) : base(text, offset) + { + _backingNode = backingNode; + } + + public override Type Type => typeof(float); + + internal override Result GetResult(IModule caller) + { + string? error = null; + var expression = _backingNode.ParameterValue; + if (expression is null || expression?.IsCompatible(typeof(float), ref error) != true) + { + return new ErrorResult(error ?? $"{_backingNode.Name} does not have an expression!", typeof(float)); + } + var ret = expression.GetValue(caller, typeof(float), ref error); + if (ret is float b) + { + return new FloatResult(b); + } + return new ErrorResult(error!, typeof(float)); + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Variables/IntegerVariable.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Variables/IntegerVariable.cs new file mode 100644 index 0000000..c0393ff --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Variables/IntegerVariable.cs @@ -0,0 +1,56 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// This class represents a boolean value that can change during +/// a model system run. +/// +internal sealed class IntegerVariable : Variable +{ + /// + /// The node that backs this variable + /// + private readonly Node _backingNode; + + public IntegerVariable(ReadOnlyMemory text, int offset, Node backingNode) : base(text, offset) + { + _backingNode = backingNode; + } + + public override Type Type => typeof(int); + + internal override Result GetResult(IModule caller) + { + string? error = null; + var expression = _backingNode.ParameterValue; + if (expression is null || expression?.IsCompatible(typeof(int), ref error) != true) + { + return new ErrorResult(error ?? $"{_backingNode.Name} does not have an expression!", typeof(int)); + } + var ret = expression.GetValue(caller, typeof(int), ref error); + if (ret is int b) + { + return new IntegerResult(b); + } + return new ErrorResult(error!, typeof(int)); + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Variables/StringVariable.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Variables/StringVariable.cs new file mode 100644 index 0000000..657ce11 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Variables/StringVariable.cs @@ -0,0 +1,56 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; + +namespace XTMF2.ModelSystemConstruct.Parameters.Compiler; + +/// +/// This class represents a boolean value that can change during +/// a model system run. +/// +internal sealed class StringVariable : Variable +{ + /// + /// The node that backs this variable + /// + private readonly Node _backingNode; + + public StringVariable(ReadOnlyMemory text, int offset, Node backingNode) : base(text, offset) + { + _backingNode = backingNode; + } + + public override Type Type => typeof(string); + + internal override Result GetResult(IModule caller) + { + string? error = null; + var expression = _backingNode.ParameterValue; + if (expression is null || expression?.IsCompatible(typeof(string), ref error) != true) + { + return new ErrorResult(error ?? $"{_backingNode.Name} does not have an expression!", typeof(string)); + } + var ret = expression.GetValue(caller, typeof(string), ref error); + if (ret is string b) + { + return new StringResult(b); + } + return new ErrorResult(error!, typeof(string)); + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/ScriptedParameter.cs b/src/XTMF2/ModelSystemConstruct/Parameters/ScriptedParameter.cs new file mode 100644 index 0000000..4b9c1b8 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/ScriptedParameter.cs @@ -0,0 +1,68 @@ +/* + Copyright 2022 University of Toronto + This file is part of XTMF2. + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; + +namespace XTMF2.ModelSystemConstruct.Parameters; + +internal class ScriptedParameter : ParameterExpression +{ + protected const string ParameterExpressionProperty = "ParameterExpression"; + private Expression _expression; + + public ScriptedParameter(Expression expression) + { + _expression = expression; + } + + public override string Representation + { + get => new (_expression.AsString()); + } + + public override object? GetValue(IModule caller, Type type, ref string? errorString) + { + if(_expression.Type != type) + { + errorString = ThrowInvalidTypes(type, _expression.Type); + return null; + } + if(ParameterCompiler.Evaluate(caller, _expression, out var ret, ref errorString)) + { + return ret; + } + return null; + } + + public override bool IsCompatible(Type type, [NotNullWhen(false)] ref string? errorString) + { + return type.IsAssignableFrom(_expression.Type); + } + + + private static string ThrowInvalidTypes(Type expected, Type expressionType) + { + return $"Invalid types, expected {expected.FullName} however the expression returned {expressionType.FullName}!"; + } + + public override Type Type => _expression.Type; + + internal override void Save(Utf8JsonWriter writer) + { + writer.WriteString(ParameterExpressionProperty, Representation); + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Start.cs b/src/XTMF2/ModelSystemConstruct/Start.cs index c6f810c..49e1faf 100644 --- a/src/XTMF2/ModelSystemConstruct/Start.cs +++ b/src/XTMF2/ModelSystemConstruct/Start.cs @@ -23,6 +23,7 @@ You should have received a copy of the GNU General Public License using XTMF2.RuntimeModules; using XTMF2.Editing; using XTMF2.Repository; +using System.Diagnostics.CodeAnalysis; namespace XTMF2.ModelSystemConstruct { @@ -60,7 +61,7 @@ private static bool FailWith(out Start? start, out string error, string message) } internal static bool Load(ModuleRepository modules, Dictionary nodes, - Boundary boundary, ref Utf8JsonReader reader, out Start? start, ref string? error) + Boundary boundary, ref Utf8JsonReader reader, [NotNullWhen(true)] out Start? start, [NotNullWhen(false)] ref string? error) { if (reader.TokenType != JsonTokenType.StartObject) { diff --git a/src/XTMF2/ModelSystemFile.cs b/src/XTMF2/ModelSystemFile.cs index 307b023..eb0feef 100644 --- a/src/XTMF2/ModelSystemFile.cs +++ b/src/XTMF2/ModelSystemFile.cs @@ -24,6 +24,7 @@ You should have received a copy of the GNU General Public License using XTMF2.Editing; using System.IO; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace XTMF2 { @@ -106,7 +107,7 @@ public sealed class ModelSystemFile /// /// Checks if the model system file is contained within a project file. /// - public bool IsContainedInProjectFile => !(_archive is null); + public bool IsContainedInProjectFile => _archive is not null; /// /// Export the model system to the given path. @@ -118,7 +119,7 @@ public sealed class ModelSystemFile /// An error message if the operation fails. /// True if the operation succeeds, false otherwise with an error message. internal static bool ExportModelSystem(ProjectSession projectSession, User user, - ModelSystemHeader modelSystemHeader, string exportPath, out CommandError? error) + ModelSystemHeader modelSystemHeader, string exportPath, [NotNullWhen(false)] out CommandError? error) { var tempDirName = string.Empty; try @@ -188,7 +189,7 @@ internal static bool ExportModelSystem(ProjectSession projectSession, User user, /// The resulting model system file, null if the operation fails. /// An error message if the operation fails. /// True if the operation succeeds, false otherwise. - internal static bool LoadModelSystemFile(string filePath, out ModelSystemFile? msf, out CommandError? error) + internal static bool LoadModelSystemFile(string filePath, [NotNullWhen(true)] out ModelSystemFile? msf, [NotNullWhen(false)] out CommandError? error) { msf = null; var toReturn = new ModelSystemFile(filePath); @@ -217,7 +218,7 @@ internal static bool LoadModelSystemFile(string filePath, out ModelSystemFile? m /// The resulting model system file. /// An error message if loading the model system file fails. /// True if the operation succeeds, false otherwise with an error message. - internal static bool LoadModelSystemFile(ZipArchive archive, string path, out ModelSystemFile? msf, out CommandError? error) + internal static bool LoadModelSystemFile(ZipArchive archive, string path, [NotNullWhen(true)] out ModelSystemFile? msf, [NotNullWhen(false)] out CommandError? error) { msf = null; try @@ -244,7 +245,7 @@ internal static bool LoadModelSystemFile(ZipArchive archive, string path, out Mo } } - private static bool LoadModelSystemFile(ModelSystemFile toReturn, Stream stream, out CommandError? error) + private static bool LoadModelSystemFile(ModelSystemFile toReturn, Stream stream, [NotNullWhen(false)] out CommandError? error) { var archive = new ZipArchive(stream, ZipArchiveMode.Read); var entry = archive.GetEntry(MetaDataFilePath); @@ -400,7 +401,7 @@ public ModelSystemFile(ZipArchive archive, string path) /// The path to try to save the model system to. /// An error message if the operation fails. /// True if the operation succeeds, false otherwise with an error message. - internal bool ExtractModelSystemTo(string modelSystemPath, out CommandError? error) + internal bool ExtractModelSystemTo(string modelSystemPath, [NotNullWhen(false)] out CommandError? error) { try { diff --git a/src/XTMF2/ModelSystemHeader.cs b/src/XTMF2/ModelSystemHeader.cs index 432c26e..3eaadec 100644 --- a/src/XTMF2/ModelSystemHeader.cs +++ b/src/XTMF2/ModelSystemHeader.cs @@ -122,10 +122,7 @@ internal static ModelSystemHeader Load(Project project, ref Utf8JsonReader reade internal static ModelSystemHeader CreateRunHeader(XTMFRuntime runtime) { - return new ModelSystemHeader(null, "Run") - { - - }; + return new ModelSystemHeader(null, "Run"); } } } diff --git a/src/XTMF2/Project.cs b/src/XTMF2/Project.cs index 1738e6e..28ecc3b 100644 --- a/src/XTMF2/Project.cs +++ b/src/XTMF2/Project.cs @@ -1,5 +1,5 @@ /* - Copyright 2017 University of Toronto + Copyright 2017-2021 University of Toronto This file is part of XTMF2. @@ -26,6 +26,7 @@ You should have received a copy of the GNU General Public License using XTMF2.Controllers; using System.Linq; using System.Text.Json; +using System.Diagnostics.CodeAnalysis; namespace XTMF2 { @@ -73,7 +74,7 @@ private Project() { } - internal static bool Load(UserController userController, string filePath, out Project project, ref string? error) + internal static bool Load(UserController userController, string filePath, [NotNullWhen(true)] out Project? project, [NotNullWhen(false)] ref string? error) { project = new Project() { @@ -193,7 +194,7 @@ internal static bool Load(UserController userController, string filePath, out Pr return false; } - internal static bool Load(ProjectFile projectFile, string projectName, User owner, out Project? project, out CommandError? error) + internal static bool Load(ProjectFile projectFile, string projectName, User owner, [NotNullWhen(true)] out Project? project, [NotNullWhen(false)] out CommandError? error) { bool deleteProject = true; Project? toReturn = null; @@ -217,9 +218,10 @@ internal static bool Load(ProjectFile projectFile, string projectName, User owne } finally { - if (deleteProject && !(toReturn is null)) + if (deleteProject && (toReturn is not null)) { - toReturn.Delete(out error); + // If we fail when deleting the project it is alright. + toReturn.Delete(out var _); } } } @@ -228,7 +230,7 @@ internal static bool Load(ProjectFile projectFile, string projectName, User owne /// Delete the project. This should only be called from the project controller /// unless the project has never been added to the project controller. /// - internal bool Delete(out CommandError? error) + internal bool Delete([NotNullWhen(false)] out CommandError? error) { try { @@ -247,7 +249,7 @@ internal bool Delete(out CommandError? error) } } - internal bool GetModelSystemHeader(string modelSystemName, out ModelSystemHeader? modelSystemHeader, out CommandError? error) + internal bool GetModelSystemHeader(string modelSystemName, [NotNullWhen(true)] out ModelSystemHeader? modelSystemHeader, [NotNullWhen(false)] out CommandError? error) { modelSystemHeader = _ModelSystems.FirstOrDefault(msh => msh.Name.Equals(modelSystemName, StringComparison.OrdinalIgnoreCase)); if (modelSystemHeader == null) @@ -269,7 +271,7 @@ internal bool ContainsModelSystem(string modelSystemName) return _ModelSystems.Any(ms => ms.Name.Equals(modelSystemName, StringComparison.OrdinalIgnoreCase)); } - private static bool SaveOrError(Project project, out CommandError? error) + private static bool SaveOrError(Project project, [NotNullWhen(false)] out CommandError? error) { string? errorString = null; if (!project.Save(ref errorString)) @@ -284,12 +286,12 @@ private static bool SaveOrError(Project project, out CommandError? error) } } - private bool SaveOrError(out CommandError? error) + private bool SaveOrError([NotNullWhen(false)] out CommandError? error) { return SaveOrError(this, out error); } - internal bool GiveOwnership(User newOwner, out CommandError? error) + internal bool GiveOwnership(User newOwner, [NotNullWhen(false)] out CommandError? error) { lock (_projectLock) { @@ -309,7 +311,7 @@ internal bool GiveOwnership(User newOwner, out CommandError? error) } } - internal bool AddAdditionalUser(User toShareWith, out CommandError? error) + internal bool AddAdditionalUser(User toShareWith, [NotNullWhen(false)] out CommandError? error) { lock (_projectLock) { @@ -324,7 +326,7 @@ internal bool AddAdditionalUser(User toShareWith, out CommandError? error) } } - internal bool RemoveAdditionalUser(User toRemove, out CommandError? error) + internal bool RemoveAdditionalUser(User toRemove, [NotNullWhen(false)] out CommandError? error) { lock (_projectLock) { @@ -339,7 +341,7 @@ internal bool RemoveAdditionalUser(User toRemove, out CommandError? error) } } - internal static bool New(User owner, string name, string description, out Project? project, out CommandError? error) + internal static bool New(User owner, string name, string description, [NotNullWhen(true)] out Project? project, [NotNullWhen(false)] out CommandError? error) { project = new Project() { @@ -367,7 +369,7 @@ private static string GetPath(User owner, string name) /// /// /// - internal bool Save(ref string? error) + internal bool Save([NotNullWhen(false)] ref string? error) { var temp = Path.GetTempFileName(); try @@ -443,7 +445,7 @@ internal bool CanAccess(User user) return user == Owner || _AdditionalUsers.Contains(user); } - internal bool SetName(string name, out CommandError? error) + internal bool SetName(string name, [NotNullWhen(false)] out CommandError? error) { if (String.IsNullOrWhiteSpace(name)) { @@ -468,7 +470,7 @@ internal bool SetName(string name, out CommandError? error) } } - internal bool SetDescription(ProjectSession session, string description, out CommandError? error) + internal bool SetDescription(ProjectSession session, string description, [NotNullWhen(false)] out CommandError? error) { Description = description; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Description))); @@ -476,7 +478,7 @@ internal bool SetDescription(ProjectSession session, string description, out Com return true; } - internal bool Remove(ProjectSession session, ModelSystemHeader modelSystemHeader, out CommandError? error) + internal bool Remove(ProjectSession session, ModelSystemHeader modelSystemHeader, [NotNullWhen(false)] out CommandError? error) { if (modelSystemHeader == null) { @@ -490,7 +492,7 @@ internal bool Remove(ProjectSession session, ModelSystemHeader modelSystemHeader return false; } - internal bool Add(ProjectSession session, ModelSystemHeader modelSystemHeader, out CommandError? error) + internal bool Add(ProjectSession session, ModelSystemHeader modelSystemHeader, [NotNullWhen(false)] out CommandError? error) { if (modelSystemHeader == null) { @@ -500,7 +502,7 @@ internal bool Add(ProjectSession session, ModelSystemHeader modelSystemHeader, o return SaveOrError(out error); } - internal bool RenameModelSystem(ModelSystemHeader modelSystem, string newName, out CommandError? error) + internal bool RenameModelSystem(ModelSystemHeader modelSystem, string newName, [NotNullWhen(false)] out CommandError? error) { if (_ModelSystems.Any(ms => newName.Equals(ms.Name, StringComparison.InvariantCultureIgnoreCase))) { @@ -519,7 +521,7 @@ internal bool RenameModelSystem(ModelSystemHeader modelSystem, string newName, o /// An error message if the operation fails. /// True if the operation succeeds, false otherwise. internal bool AddModelSystemFromModelSystemFile(string modelSystemName, - ModelSystemFile msf, out ModelSystemHeader? header, out CommandError? error) + ModelSystemFile msf, [NotNullWhen(true)] out ModelSystemHeader? header, [NotNullWhen(false)] out CommandError? error) { header = null; if (msf is null) diff --git a/src/XTMF2/ProjectFile.cs b/src/XTMF2/ProjectFile.cs index efbcafa..0ebbc57 100644 --- a/src/XTMF2/ProjectFile.cs +++ b/src/XTMF2/ProjectFile.cs @@ -20,6 +20,7 @@ You should have received a copy of the GNU General Public License using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Compression; using System.Text.Json; @@ -117,14 +118,8 @@ private ProjectFile(string filePath) /// True if the operation completes successfully, false otherwise with an error message. internal static bool ExportProject(ProjectSession projectSession, User user, string exportPath, out CommandError? error) { - if (projectSession is null) - { - throw new ArgumentNullException(nameof(projectSession)); - } - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } + ArgumentNullException.ThrowIfNull(projectSession); + ArgumentNullException.ThrowIfNull(user); var project = projectSession.Project; // we need this declared outside of the @@ -221,7 +216,7 @@ private static void WriteMetaData(string tempDirName, Project project, User user /// The resulting project. /// An error message if the operation fails. /// True if the operation succeeds, false otherwise with an error message. - internal static bool ImportProject(User owner, string name, string filePath, out Project? project, out CommandError? error) + internal static bool ImportProject(User owner, string name, string filePath, [NotNullWhen(true)] out Project? project, [NotNullWhen(false)] out CommandError? error) { project = null; try @@ -245,7 +240,7 @@ internal static bool ImportProject(User owner, string name, string filePath, out return false; } - private static bool LoadMetaData(ProjectFile projectFile, ZipArchive archive, out CommandError? error) + private static bool LoadMetaData(ProjectFile projectFile, ZipArchive archive, [NotNullWhen(false)] out CommandError? error) { try { diff --git a/src/XTMF2/Repository/ProjectRepository.cs b/src/XTMF2/Repository/ProjectRepository.cs index 57a1a21..ee915b4 100644 --- a/src/XTMF2/Repository/ProjectRepository.cs +++ b/src/XTMF2/Repository/ProjectRepository.cs @@ -22,6 +22,7 @@ You should have received a copy of the GNU General Public License using System.Text; using System.Linq; using XTMF2.Editing; +using System.Diagnostics.CodeAnalysis; namespace XTMF2.Repository { @@ -59,7 +60,7 @@ where project.CanAccess(user) /// A message containing a description of the error /// The returned project /// True if successful, false otherwise with an error message. - internal bool CreateNew(string name, User owner, out Project? ret, out CommandError? error) + internal bool CreateNew(string name, User owner, [NotNullWhen(true)] out Project? ret, [NotNullWhen(false)] out CommandError? error) { if (!Project.New(owner, name, string.Empty, out ret, out error)) { diff --git a/src/XTMF2/RuntimeModules/ScriptedParameter.cs b/src/XTMF2/RuntimeModules/ScriptedParameter.cs new file mode 100644 index 0000000..ad3297d --- /dev/null +++ b/src/XTMF2/RuntimeModules/ScriptedParameter.cs @@ -0,0 +1,69 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ + +using System.Diagnostics.CodeAnalysis; +using XTMF2.ModelSystemConstruct; + +namespace XTMF2.RuntimeModules +{ + [Module(Name = "Scripted Parameter", DocumentationLink = "http://tmg.utoronto.ca/doc/2.0", + Description = "Provides the ability to have a value that is calculated in an expression.")] + public sealed class ScriptedParameter : BaseFunction + { +#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. + public ParameterExpression Expression; +#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. + + public override T Invoke() + { + string? error = null; + if (!Expression.IsCompatible(typeof(T), ref error)) + { + Throw(error); + } + var ret = Expression.GetValue(this, typeof(T), ref error); + if (ret is null) + { + ThrowGotNull(); + } + return (T)ret; + } + + /// + /// + /// + /// + /// The requested error message. + [DoesNotReturn] + private void Throw(string error) + { + throw new XTMFRuntimeException(this, error); + } + + /// + /// + /// + /// + [DoesNotReturn] + private void ThrowGotNull() + { + throw new XTMFRuntimeException(this, $"Unable to get a {typeof(T).FullName} value from expression '{Expression.Representation}'!"); + } + } +} diff --git a/src/XTMF2/XTMF2.csproj b/src/XTMF2/XTMF2.csproj index 0cb7908..7dca667 100644 --- a/src/XTMF2/XTMF2.csproj +++ b/src/XTMF2/XTMF2.csproj @@ -1,7 +1,7 @@  - net6.0 + net10.0 true https://github.com/TravelModellingGroup/XTMF2.git @@ -12,12 +12,6 @@ enable - - - - - - diff --git a/tests/XTMF2.UnitTests/Editing/TestBoundaries.cs b/tests/XTMF2.UnitTests/Editing/TestBoundaries.cs index 150b99a..028fce0 100644 --- a/tests/XTMF2.UnitTests/Editing/TestBoundaries.cs +++ b/tests/XTMF2.UnitTests/Editing/TestBoundaries.cs @@ -33,14 +33,14 @@ public void AddBoundary() { var ms = mSession.ModelSystem; CommandError error = null; - Assert.AreEqual(0, ms.GlobalBoundary.Boundaries.Count); + Assert.IsEmpty(ms.GlobalBoundary.Boundaries); Assert.IsTrue(mSession.AddBoundary(user, ms.GlobalBoundary, "UniqueName", out Boundary subB, out error), error?.Message); Assert.IsFalse(mSession.AddBoundary(user, ms.GlobalBoundary, "UniqueName", out Boundary fail1, out error), "Created a second boundary with the same name!"); - Assert.AreEqual(1, ms.GlobalBoundary.Boundaries.Count); + Assert.HasCount(1, ms.GlobalBoundary.Boundaries); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Boundaries.Count); + Assert.HasCount(0, ms.GlobalBoundary.Boundaries); Assert.IsTrue(mSession.Redo(user, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Boundaries.Count); + Assert.HasCount(1, ms.GlobalBoundary.Boundaries); Assert.AreSame(subB, ms.GlobalBoundary.Boundaries[0]); Assert.IsFalse(mSession.AddBoundary(user, ms.GlobalBoundary, "UniqueName", out Boundary fail2, out error), "Created a second boundary with the same name after redo!"); }); @@ -52,7 +52,7 @@ public void AddBoundaryWithBadUser() TestHelper.RunInModelSystemContext("AddBoundaryWithBadUser", (user, unauthorizedUser, pSession, mSession) => { var ms = mSession.ModelSystem; - Assert.AreEqual(0, ms.GlobalBoundary.Boundaries.Count); + Assert.IsEmpty(ms.GlobalBoundary.Boundaries); Assert.IsFalse(mSession.AddBoundary(unauthorizedUser, ms.GlobalBoundary, "UniqueName", out Boundary subB, out var error), error?.Message); }); } @@ -63,8 +63,8 @@ public void AddBoundaryNullParent() TestHelper.RunInModelSystemContext("AddBoundaryNullParent", (user, pSession, mSession) => { var ms = mSession.ModelSystem; - Assert.AreEqual(0, ms.GlobalBoundary.Boundaries.Count); - Assert.ThrowsException(() => + Assert.IsEmpty(ms.GlobalBoundary.Boundaries); + Assert.Throws(() => { mSession.AddBoundary(user, null, "UniqueName", out Boundary subB, out var error); }); @@ -77,8 +77,8 @@ public void AddBoundaryNulUser() TestHelper.RunInModelSystemContext("AddBoundaryNullUser", (user, pSession, mSession) => { var ms = mSession.ModelSystem; - Assert.AreEqual(0, ms.GlobalBoundary.Boundaries.Count); - Assert.ThrowsException(() => + Assert.IsEmpty(ms.GlobalBoundary.Boundaries); + Assert.Throws(() => { mSession.AddBoundary(null, ms.GlobalBoundary, "UniqueName", out Boundary subB, out var error); }); @@ -91,25 +91,25 @@ public void RemoveBoundary() TestHelper.RunInModelSystemContext("RemoveBoundary", (user, pSession, mSession) => { var ms = mSession.ModelSystem; - Assert.AreEqual(0, ms.GlobalBoundary.Boundaries.Count); + Assert.IsEmpty(ms.GlobalBoundary.Boundaries); Assert.IsTrue(mSession.AddBoundary(user, ms.GlobalBoundary, "UniqueName", out Boundary subB, out var error), error?.Message); Assert.IsFalse(mSession.AddBoundary(user, ms.GlobalBoundary, "UniqueName", out Boundary fail1, out error), "Created a second boundary with the same name!"); - Assert.AreEqual(1, ms.GlobalBoundary.Boundaries.Count); + Assert.HasCount(1, ms.GlobalBoundary.Boundaries); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Boundaries.Count); + Assert.HasCount(0, ms.GlobalBoundary.Boundaries); Assert.IsTrue(mSession.Redo(user, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Boundaries.Count); + Assert.HasCount(1, ms.GlobalBoundary.Boundaries); Assert.AreSame(subB, ms.GlobalBoundary.Boundaries[0]); Assert.IsFalse(mSession.AddBoundary(user, ms.GlobalBoundary, "UniqueName", out Boundary fail2, out error), "Created a second boundary with the same name after redo!"); // Now test removing the boundary explicitly Assert.IsTrue(mSession.RemoveBoundary(user, ms.GlobalBoundary, subB, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Boundaries.Count); + Assert.IsEmpty(ms.GlobalBoundary.Boundaries); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Boundaries.Count); + Assert.HasCount(1, ms.GlobalBoundary.Boundaries); Assert.AreSame(subB, ms.GlobalBoundary.Boundaries[0]); Assert.IsTrue(mSession.Redo(user, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Boundaries.Count); + Assert.IsEmpty(ms.GlobalBoundary.Boundaries); }); } @@ -120,21 +120,21 @@ public void RemoveBoundaryWithBadUser() { var ms = mSession.ModelSystem; CommandError error = null; - Assert.AreEqual(0, ms.GlobalBoundary.Boundaries.Count); + Assert.IsEmpty(ms.GlobalBoundary.Boundaries); Assert.IsTrue(mSession.AddBoundary(user, ms.GlobalBoundary, "UniqueName", out Boundary subB, out error), error?.Message); Assert.IsFalse(mSession.AddBoundary(user, ms.GlobalBoundary, "UniqueName", out Boundary fail1, out error), "Created a second boundary with the same name!"); - Assert.AreEqual(1, ms.GlobalBoundary.Boundaries.Count); + Assert.HasCount(1, ms.GlobalBoundary.Boundaries); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Boundaries.Count); + Assert.IsEmpty(ms.GlobalBoundary.Boundaries); Assert.IsTrue(mSession.Redo(user, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Boundaries.Count); + Assert.HasCount(1, ms.GlobalBoundary.Boundaries); Assert.AreSame(subB, ms.GlobalBoundary.Boundaries[0]); Assert.IsFalse(mSession.AddBoundary(user, ms.GlobalBoundary, "UniqueName", out Boundary fail2, out error), "Created a second boundary with the same name after redo!"); // Now test removing the boundary explicitly - Assert.AreEqual(1, ms.GlobalBoundary.Boundaries.Count); + Assert.HasCount(1, ms.GlobalBoundary.Boundaries); Assert.IsFalse(mSession.RemoveBoundary(unauthorizedUser, ms.GlobalBoundary, subB, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Boundaries.Count); + Assert.HasCount(1, ms.GlobalBoundary.Boundaries); }); } @@ -144,12 +144,12 @@ public void RemoveBoundaryNullBoundary() TestHelper.RunInModelSystemContext("RemoveBoundary", (user, pSession, mSession) => { var ms = mSession.ModelSystem; - Assert.AreEqual(0, ms.GlobalBoundary.Boundaries.Count); + Assert.IsEmpty(ms.GlobalBoundary.Boundaries); Assert.IsTrue(mSession.AddBoundary(user, ms.GlobalBoundary, "UniqueName", out Boundary subB, out var error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Boundaries.Count); + Assert.HasCount(1, ms.GlobalBoundary.Boundaries); // Now test removing the boundary explicitly - Assert.ThrowsException(() => + Assert.Throws(() => { mSession.RemoveBoundary(user, ms.GlobalBoundary, null, out error); }); @@ -162,12 +162,12 @@ public void RemoveBoundaryNullParent() TestHelper.RunInModelSystemContext("RemoveBoundary", (user, pSession, mSession) => { var ms = mSession.ModelSystem; - Assert.AreEqual(0, ms.GlobalBoundary.Boundaries.Count); + Assert.IsEmpty(ms.GlobalBoundary.Boundaries); Assert.IsTrue(mSession.AddBoundary(user, ms.GlobalBoundary, "UniqueName", out Boundary subB, out var error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Boundaries.Count); + Assert.HasCount(1, ms.GlobalBoundary.Boundaries); // Now test removing the boundary explicitly - Assert.ThrowsException(() => + Assert.Throws(() => { mSession.RemoveBoundary(user, null, subB, out error); }); @@ -181,12 +181,12 @@ public void RemoveBoundaryNullUser() { var ms = mSession.ModelSystem; CommandError error = null; - Assert.AreEqual(0, ms.GlobalBoundary.Boundaries.Count); + Assert.IsEmpty(ms.GlobalBoundary.Boundaries); Assert.IsTrue(mSession.AddBoundary(user, ms.GlobalBoundary, "UniqueName", out Boundary subB, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Boundaries.Count); + Assert.HasCount(1, ms.GlobalBoundary.Boundaries); // Now test removing the boundary explicitly - Assert.ThrowsException(() => + Assert.Throws(() => { mSession.RemoveBoundary(null, ms.GlobalBoundary, subB, out error); }); @@ -200,12 +200,12 @@ public void RemoveBoundaryNotInBoundary() { var ms = mSession.ModelSystem; CommandError error = null; - Assert.AreEqual(0, ms.GlobalBoundary.Boundaries.Count); + Assert.IsEmpty(ms.GlobalBoundary.Boundaries); Assert.IsTrue(mSession.AddBoundary(user, ms.GlobalBoundary, "SubB", out Boundary subB, out error), error?.Message); Assert.IsTrue(mSession.AddBoundary(user, ms.GlobalBoundary, "SubC", out Boundary subC, out error), error?.Message); Assert.IsTrue(mSession.AddBoundary(user, subC, "SubCA", out Boundary subCA, out error), error?.Message); - Assert.AreEqual(2, ms.GlobalBoundary.Boundaries.Count); - Assert.AreEqual(1, subC.Boundaries.Count); + Assert.HasCount(2, ms.GlobalBoundary.Boundaries); + Assert.HasCount(1, subC.Boundaries); Assert.IsFalse(mSession.RemoveBoundary(user, ms.GlobalBoundary, subCA, out error), "Successfully removed a boundary from a grandparent isntead of failing!"); }); diff --git a/tests/XTMF2.UnitTests/Editing/TestCommentBlock.cs b/tests/XTMF2.UnitTests/Editing/TestCommentBlock.cs index 156738c..9bc8f75 100644 --- a/tests/XTMF2.UnitTests/Editing/TestCommentBlock.cs +++ b/tests/XTMF2.UnitTests/Editing/TestCommentBlock.cs @@ -36,9 +36,9 @@ public void TestCreatingCommentBlock() var comment = "My Comment"; var location = new Rectangle(100, 100); var comBlocks = ms.GlobalBoundary.CommentBlocks; - Assert.AreEqual(0, comBlocks.Count); + Assert.IsEmpty(comBlocks); Assert.IsTrue(mSession.AddCommentBlock(user, ms.GlobalBoundary, comment, location, out CommentBlock block, out error), error?.Message); - Assert.AreEqual(1, comBlocks.Count); + Assert.HasCount(1, comBlocks); Assert.AreEqual(comment, comBlocks[0].Comment); Assert.AreEqual(location, comBlocks[0].Location); }); @@ -54,9 +54,9 @@ public void TestCreatingCommentBlockWithBadUser() var comment = "My Comment"; var location = new Rectangle(100, 100); var comBlocks = ms.GlobalBoundary.CommentBlocks; - Assert.AreEqual(0, comBlocks.Count); + Assert.IsEmpty(comBlocks); Assert.IsFalse(mSession.AddCommentBlock(unauthorizedUser, ms.GlobalBoundary, comment, location, out CommentBlock block, out error), error?.Message); - Assert.AreEqual(0, comBlocks.Count); + Assert.IsEmpty(comBlocks); }); } @@ -70,9 +70,9 @@ public void TestCommentBlockPersistence() CommandError error = null; var ms = msSession.ModelSystem; var comBlocks = ms.GlobalBoundary.CommentBlocks; - Assert.AreEqual(0, comBlocks.Count); + Assert.IsEmpty(comBlocks); Assert.IsTrue(msSession.AddCommentBlock(user, ms.GlobalBoundary, comment, location, out CommentBlock block, out error), error?.Message); - Assert.AreEqual(1, comBlocks.Count); + Assert.HasCount(1, comBlocks); Assert.AreEqual(comment, comBlocks[0].Comment); Assert.AreEqual(location, comBlocks[0].Location); Assert.IsTrue(msSession.Save(out error), error?.Message); @@ -80,7 +80,7 @@ public void TestCommentBlockPersistence() { var ms = msSession.ModelSystem; var comBlocks = ms.GlobalBoundary.CommentBlocks; - Assert.AreEqual(1, comBlocks.Count); + Assert.HasCount(1, comBlocks); Assert.AreEqual(comment, comBlocks[0].Comment); Assert.AreEqual(location, comBlocks[0].Location); }); @@ -96,13 +96,13 @@ public void TestRemovingCommentBlock() var comment = "My Comment"; var location = new Rectangle(100, 100); var comBlocks = ms.GlobalBoundary.CommentBlocks; - Assert.AreEqual(0, comBlocks.Count); + Assert.IsEmpty(comBlocks); Assert.IsTrue(msSession.AddCommentBlock(user, ms.GlobalBoundary, comment, location, out CommentBlock block, out error), error?.Message); - Assert.AreEqual(1, comBlocks.Count); + Assert.HasCount(1, comBlocks); Assert.AreEqual(comment, comBlocks[0].Comment); Assert.AreEqual(location, comBlocks[0].Location); Assert.IsTrue(msSession.RemoveCommentBlock(user, ms.GlobalBoundary, block, out error), error?.Message); - Assert.AreEqual(0, comBlocks.Count); + Assert.IsEmpty(comBlocks); }); } @@ -116,13 +116,13 @@ public void TestRemovingCommentBlockWithBadUser() var comment = "My Comment"; var location = new Rectangle(100, 100); var comBlocks = ms.GlobalBoundary.CommentBlocks; - Assert.AreEqual(0, comBlocks.Count); + Assert.IsEmpty(comBlocks); Assert.IsTrue(msSession.AddCommentBlock(user, ms.GlobalBoundary, comment, location, out CommentBlock block, out error), error?.Message); - Assert.AreEqual(1, comBlocks.Count); + Assert.HasCount(1, comBlocks); Assert.AreEqual(comment, comBlocks[0].Comment); Assert.AreEqual(location, comBlocks[0].Location); Assert.IsFalse(msSession.RemoveCommentBlock(unauthorizedUser, ms.GlobalBoundary, block, out error), error?.Message); - Assert.AreEqual(1, comBlocks.Count); + Assert.HasCount(1, comBlocks); }); } @@ -136,15 +136,15 @@ public void TestCreatingCommentBlockUndoRedo() var comment = "My Comment"; var location = new Rectangle(100, 100); var comBlock = ms.GlobalBoundary.CommentBlocks; - Assert.AreEqual(0, comBlock.Count); + Assert.IsEmpty(comBlock); Assert.IsTrue(msSession.AddCommentBlock(user, ms.GlobalBoundary, comment, location, out CommentBlock block, out error), error?.Message); - Assert.AreEqual(1, comBlock.Count); + Assert.HasCount(1, comBlock); Assert.AreEqual(comment, comBlock[0].Comment); Assert.AreEqual(location, comBlock[0].Location); Assert.IsTrue(msSession.Undo(user, out error), error?.Message); - Assert.AreEqual(0, comBlock.Count); + Assert.IsEmpty(comBlock); Assert.IsTrue(msSession.Redo(user, out error), error?.Message); - Assert.AreEqual(1, comBlock.Count); + Assert.HasCount(1, comBlock); Assert.AreEqual(comment, comBlock[0].Comment); }); } @@ -159,19 +159,19 @@ public void TestRemovingCommentBlockUndoRedo() var comment = "My Comment"; var location = new Rectangle(100, 100); var comBlocks = ms.GlobalBoundary.CommentBlocks; - Assert.AreEqual(0, comBlocks.Count); + Assert.IsEmpty(comBlocks); Assert.IsTrue(msSession.AddCommentBlock(user, ms.GlobalBoundary, comment, location, out CommentBlock block, out error), error?.Message); - Assert.AreEqual(1, comBlocks.Count); + Assert.HasCount(1, comBlocks); Assert.AreEqual(comment, comBlocks[0].Comment); Assert.AreEqual(location, comBlocks[0].Location); Assert.IsTrue(msSession.RemoveCommentBlock(user, ms.GlobalBoundary, block, out error), error?.Message); - Assert.AreEqual(0, comBlocks.Count); + Assert.IsEmpty(comBlocks); Assert.IsTrue(msSession.Undo(user, out error), error?.Message); - Assert.AreEqual(1, comBlocks.Count); + Assert.HasCount(1, comBlocks); Assert.AreEqual(comment, comBlocks[0].Comment); Assert.AreEqual(location, comBlocks[0].Location); Assert.IsTrue(msSession.Redo(user, out error), error?.Message); - Assert.AreEqual(0, comBlocks.Count); + Assert.IsEmpty(comBlocks); }); } @@ -186,9 +186,9 @@ public void TestChangingCommentBlockText() var newComment = "New comment"; var location = new Rectangle(100, 100); var comBlocks = ms.GlobalBoundary.CommentBlocks; - Assert.AreEqual(0, comBlocks.Count); + Assert.IsEmpty(comBlocks); Assert.IsTrue(msSession.AddCommentBlock(user, ms.GlobalBoundary, comment, location, out CommentBlock block, out error), error?.Message); - Assert.AreEqual(1, comBlocks.Count); + Assert.HasCount(1, comBlocks); Assert.AreEqual(comment, comBlocks[0].Comment); Assert.IsTrue(msSession.SetCommentBlockText(user, block, newComment, out error), error?.Message); Assert.AreEqual(newComment, block.Comment, "The comment block's text was not set!"); @@ -210,9 +210,9 @@ public void TestChangingCommentBlockPosition() var location = new Rectangle(100, 100); var newLocation = new Rectangle(100, 200); var comBlocks = ms.GlobalBoundary.CommentBlocks; - Assert.AreEqual(0, comBlocks.Count); + Assert.IsEmpty(comBlocks); Assert.IsTrue(msSession.AddCommentBlock(user, ms.GlobalBoundary, comment, location, out CommentBlock block, out error), error?.Message); - Assert.AreEqual(1, comBlocks.Count); + Assert.HasCount(1, comBlocks); Assert.AreEqual(comment, comBlocks[0].Comment); Assert.IsTrue(msSession.SetCommentBlockLocation(user, block, newLocation, out error), error?.Message); Assert.AreEqual(newLocation, block.Location, "The comment block's location was not set!"); diff --git a/tests/XTMF2.UnitTests/Editing/TestEditingParameterExpressions.cs b/tests/XTMF2.UnitTests/Editing/TestEditingParameterExpressions.cs new file mode 100644 index 0000000..6bf9719 --- /dev/null +++ b/tests/XTMF2.UnitTests/Editing/TestEditingParameterExpressions.cs @@ -0,0 +1,139 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Linq; +using XTMF2.Editing; +using XTMF2.ModelSystemConstruct; +using XTMF2.RuntimeModules; +using XTMF2.UnitTests.Modules; + +namespace XTMF2.UnitTests.Editing; + +[TestClass] +public class TestEditingParameterExpressions +{ + [TestMethod] + public void ParameterExpression() + { + TestHelper.RunInModelSystemContext("SetModuleToUseParameterExpression", (user, pSession, mSession) => + { + CommandError error = null; + string errorStr = null; + var ms = mSession.ModelSystem; + var gBound = ms.GlobalBoundary; + Assert.IsTrue(mSession.AddNodeGenerateParameters(user, ms.GlobalBoundary, "Test", + typeof(SimpleParameterModule), Rectangle.Hidden, out var node, out var children, out error), error?.Message); + Assert.IsNotNull(children); + var childNode = children.FirstOrDefault(n => n.Name == "Real Function"); + Assert.IsNotNull(childNode); + Assert.IsTrue(mSession.SetParameterExpression(user, childNode, "\"Hello World\" + (1 + 2)", out error)); + Assert.IsNotNull(childNode.ParameterValue); + Assert.AreEqual(typeof(string), childNode.ParameterValue.Type); + Assert.IsInstanceOfType(childNode.ParameterValue, typeof(ParameterExpression)); + Assert.AreEqual("Hello World3", childNode.ParameterValue.GetValue(null, typeof(string), ref errorStr)); + }); + } + + [TestMethod] + public void ParameterExpressionUndo() + { + TestHelper.RunInModelSystemContext("SetModuleToUseParameterExpression", (user, pSession, mSession) => + { + CommandError error = null; + string errorStr = null; + var ms = mSession.ModelSystem; + var gBound = ms.GlobalBoundary; + Assert.IsTrue(mSession.AddNodeGenerateParameters(user, ms.GlobalBoundary, "Test", + typeof(SimpleParameterModule), Rectangle.Hidden, out var node, out var children, out error), error?.Message); + Assert.IsNotNull(children); + var childNode = children.FirstOrDefault(n => n.Name == "Real Function"); + Assert.IsNotNull(childNode); + Assert.IsTrue(mSession.SetParameterValue(user, childNode, "OriginalValue", out error)); + Assert.IsTrue(mSession.SetParameterExpression(user, childNode, "\"Hello World\" + (1 + 2)", out error)); + Assert.IsNotNull(childNode.ParameterValue); + Assert.AreEqual(typeof(string), childNode.ParameterValue.Type); + Assert.IsInstanceOfType(childNode.ParameterValue, typeof(ParameterExpression)); + Assert.AreEqual("Hello World3", childNode.ParameterValue.GetValue(null, typeof(string), ref errorStr)); + Assert.IsTrue(mSession.Undo(user, out error), error?.Message); + Assert.AreEqual("OriginalValue", childNode.ParameterValue.GetValue(null, typeof(string), ref errorStr)); + }); + } + + [TestMethod] + public void ParameterExpressionRedo() + { + TestHelper.RunInModelSystemContext("SetModuleToUseParameterExpression", (user, pSession, mSession) => + { + CommandError error = null; + string errorStr = null; + var ms = mSession.ModelSystem; + var gBound = ms.GlobalBoundary; + Assert.IsTrue(mSession.AddNodeGenerateParameters(user, ms.GlobalBoundary, "Test", + typeof(SimpleParameterModule), Rectangle.Hidden, out var node, out var children, out error), error?.Message); + Assert.IsNotNull(children); + var childNode = children.FirstOrDefault(n => n.Name == "Real Function"); + Assert.IsNotNull(childNode); + Assert.IsTrue(mSession.SetParameterValue(user, childNode, "OriginalValue", out error)); + Assert.IsTrue(mSession.SetParameterExpression(user, childNode, "\"Hello World\" + (1 + 2)", out error)); + Assert.IsNotNull(childNode.ParameterValue); + Assert.AreEqual(typeof(string), childNode.ParameterValue.Type); + Assert.IsInstanceOfType(childNode.ParameterValue, typeof(ParameterExpression)); + Assert.AreEqual("Hello World3", childNode.ParameterValue.GetValue(null, typeof(string), ref errorStr)); + Assert.IsTrue(mSession.Undo(user, out error), error?.Message); + Assert.AreEqual("OriginalValue", childNode.ParameterValue.GetValue(null, typeof(string), ref errorStr)); + Assert.IsTrue(mSession.Redo(user, out error), error?.Message); + Assert.AreEqual("Hello World3", childNode.ParameterValue.GetValue(null, typeof(string), ref errorStr)); + }); + } + + [TestMethod] + public void ParameterExpressionSaved() + { + TestHelper.RunInModelSystemContext("SetModuleToUseParameterExpression", (user, pSession, mSession) => + { + CommandError error = null; + string errorStr = null; + var ms = mSession.ModelSystem; + var gBound = ms.GlobalBoundary; + Assert.IsTrue(mSession.AddNodeGenerateParameters(user, ms.GlobalBoundary, "Test", + typeof(SimpleParameterModule), Rectangle.Hidden, out var node, out var children, out error), error?.Message); + Assert.IsNotNull(children); + var childNode = children.FirstOrDefault(n => n.Name == "Real Function"); + Assert.IsNotNull(childNode); + Assert.IsTrue(mSession.SetParameterExpression(user, childNode, "\"Hello World\" + (1 + 2)", out error)); + Assert.IsNotNull(childNode.ParameterValue); + Assert.AreEqual(typeof(string), childNode.ParameterValue.Type); + Assert.IsInstanceOfType(childNode.ParameterValue, typeof(ParameterExpression)); + Assert.AreEqual("Hello World3", childNode.ParameterValue.GetValue(null, typeof(string), ref errorStr)); + },(user, pSession, mSession)=> + { + string errorStr = null; + var ms = mSession.ModelSystem; + var gBound = ms.GlobalBoundary; + var childNode = gBound.Modules.FirstOrDefault(n => n.Name == "Real Function"); + Assert.IsNotNull(childNode); + Assert.IsNotNull(childNode.ParameterValue); + Assert.AreEqual("\"Hello World\" + (1 + 2)", childNode.ParameterValue.Representation); + Assert.AreEqual("Hello World3", childNode.ParameterValue.GetValue(null, typeof(string), ref errorStr)); + }); + } +} + diff --git a/tests/XTMF2.UnitTests/Editing/TestFunctions.cs b/tests/XTMF2.UnitTests/Editing/TestFunctions.cs index 7efff35..5835620 100644 --- a/tests/XTMF2.UnitTests/Editing/TestFunctions.cs +++ b/tests/XTMF2.UnitTests/Editing/TestFunctions.cs @@ -35,9 +35,9 @@ public void TestAddFunctionTemplate() var ms = mSession.ModelSystem; var name = "FunctionTemplateName"; var functionTemplates = ms.GlobalBoundary.FunctionTemplates; - Assert.AreEqual(0, functionTemplates.Count); + Assert.IsEmpty(functionTemplates); Assert.IsTrue(mSession.AddFunctionTemplate(user, ms.GlobalBoundary, name, out FunctionTemplate template, out error), error?.Message); - Assert.AreEqual(1, functionTemplates.Count); + Assert.HasCount(1, functionTemplates); }); } @@ -50,13 +50,13 @@ public void TestAddFunctionTemplateUndo() var ms = mSession.ModelSystem; var name = "FunctionTemplateName"; var functionTemplates = ms.GlobalBoundary.FunctionTemplates; - Assert.AreEqual(0, functionTemplates.Count); + Assert.IsEmpty(functionTemplates); Assert.IsTrue(mSession.AddFunctionTemplate(user, ms.GlobalBoundary, name, out FunctionTemplate template, out error), error?.Message); - Assert.AreEqual(1, functionTemplates.Count); + Assert.HasCount(1, functionTemplates); Assert.IsTrue(mSession.Undo(user, out error)); - Assert.AreEqual(0, functionTemplates.Count); + Assert.IsEmpty(functionTemplates); Assert.IsTrue(mSession.Redo(user, out error)); - Assert.AreEqual(1, functionTemplates.Count); + Assert.HasCount(1, functionTemplates); }); } @@ -69,11 +69,11 @@ public void TestRemoveFunctionTemplate() var ms = mSession.ModelSystem; var name = "FunctionTemplateName"; var functionTemplates = ms.GlobalBoundary.FunctionTemplates; - Assert.AreEqual(0, functionTemplates.Count); + Assert.IsEmpty(functionTemplates); Assert.IsTrue(mSession.AddFunctionTemplate(user, ms.GlobalBoundary, name, out FunctionTemplate template, out error), error?.Message); - Assert.AreEqual(1, functionTemplates.Count); + Assert.HasCount(1, functionTemplates); Assert.IsTrue(mSession.RemoveFunctionTemplate(user, ms.GlobalBoundary, template, out error), error?.Message); - Assert.AreEqual(0, functionTemplates.Count); + Assert.IsEmpty(functionTemplates); }); } @@ -86,15 +86,15 @@ public void TestRemoveFunctionTemplateUndo() var ms = mSession.ModelSystem; var name = "FunctionTemplateName"; var functionTemplates = ms.GlobalBoundary.FunctionTemplates; - Assert.AreEqual(0, functionTemplates.Count); + Assert.IsEmpty(functionTemplates); Assert.IsTrue(mSession.AddFunctionTemplate(user, ms.GlobalBoundary, name, out FunctionTemplate template, out error), error?.Message); - Assert.AreEqual(1, functionTemplates.Count); + Assert.HasCount(1, functionTemplates); Assert.IsTrue(mSession.RemoveFunctionTemplate(user, ms.GlobalBoundary, template, out error), error?.Message); - Assert.AreEqual(0, functionTemplates.Count); + Assert.IsEmpty(functionTemplates); Assert.IsTrue(mSession.Undo(user, out error)); - Assert.AreEqual(1, functionTemplates.Count); + Assert.HasCount(1, functionTemplates); Assert.IsTrue(mSession.Redo(user, out error)); - Assert.AreEqual(0, functionTemplates.Count); + Assert.IsEmpty(functionTemplates); }); } @@ -107,15 +107,15 @@ public void TestFunctionTemplateSave() var ms = mSession.ModelSystem; var name = "FunctionTemplateName"; var functionTemplates = ms.GlobalBoundary.FunctionTemplates; - Assert.AreEqual(0, functionTemplates.Count); + Assert.IsEmpty(functionTemplates); Assert.IsTrue(mSession.AddFunctionTemplate(user, ms.GlobalBoundary, name, out FunctionTemplate template, out error), error?.Message); - Assert.AreEqual(1, functionTemplates.Count); + Assert.HasCount(1, functionTemplates); Assert.IsTrue(mSession.Save(out error), error?.Message); }, (user, pSession, mSession)=> { var ms = mSession.ModelSystem; var functionTemplates = ms.GlobalBoundary.FunctionTemplates; - Assert.AreEqual(1, functionTemplates.Count, "The function template was not saved!"); + Assert.HasCount(1, functionTemplates, "The function template was not saved!"); }); } } diff --git a/tests/XTMF2.UnitTests/Editing/TestLinks.cs b/tests/XTMF2.UnitTests/Editing/TestLinks.cs index 9a5ecd7..8b3f675 100644 --- a/tests/XTMF2.UnitTests/Editing/TestLinks.cs +++ b/tests/XTMF2.UnitTests/Editing/TestLinks.cs @@ -37,19 +37,19 @@ public void UndoAddLink() { var ms = mSession.ModelSystem; CommandError error = null; - Assert.AreEqual(0, ms.GlobalBoundary.Modules.Count); + Assert.IsEmpty(ms.GlobalBoundary.Modules); Assert.IsTrue(mSession.AddNode(user, ms.GlobalBoundary, "Start", typeof(BasicParameter), Rectangle.Hidden, out var parameter, out error), error?.Message); Assert.IsTrue(mSession.AddNode(user, ms.GlobalBoundary, "Start", typeof(SimpleParameterModule), Rectangle.Hidden, out var module, out error), error?.Message); - Assert.AreEqual(2, ms.GlobalBoundary.Modules.Count); - Assert.AreEqual(0, ms.GlobalBoundary.Links.Count); + Assert.HasCount(2, ms.GlobalBoundary.Modules); + Assert.IsEmpty(ms.GlobalBoundary.Links); Assert.IsTrue(mSession.AddLink(user, module, module.Hooks[0], parameter, out var link, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Links.Count); + Assert.HasCount(1, ms.GlobalBoundary.Links); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Links.Count); + Assert.IsEmpty(ms.GlobalBoundary.Links); Assert.IsTrue(mSession.Redo(user, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Links.Count); + Assert.HasCount(1, ms.GlobalBoundary.Links); Assert.AreSame(link, ms.GlobalBoundary.Links[0]); }); } @@ -61,24 +61,24 @@ public void RemoveLink() { var ms = mSession.ModelSystem; CommandError error = null; - Assert.AreEqual(0, ms.GlobalBoundary.Modules.Count); + Assert.IsEmpty(ms.GlobalBoundary.Modules); Assert.IsTrue(mSession.AddNode(user, ms.GlobalBoundary, "Start", typeof(BasicParameter), Rectangle.Hidden, out var parameter, out error), error?.Message); Assert.IsTrue(mSession.AddNode(user, ms.GlobalBoundary, "Start", typeof(SimpleParameterModule), Rectangle.Hidden, out var module, out error), error?.Message); - Assert.AreEqual(2, ms.GlobalBoundary.Modules.Count); - Assert.AreEqual(0, ms.GlobalBoundary.Links.Count); + Assert.HasCount(2, ms.GlobalBoundary.Modules); + Assert.IsEmpty(ms.GlobalBoundary.Links); Assert.IsTrue(mSession.AddLink(user, module, module.Hooks[0], parameter, out var link, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Links.Count); + Assert.HasCount(1, ms.GlobalBoundary.Links); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Links.Count); + Assert.IsEmpty(ms.GlobalBoundary.Links); Assert.IsTrue(mSession.Redo(user, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Links.Count); + Assert.HasCount(1, ms.GlobalBoundary.Links); Assert.AreSame(link, ms.GlobalBoundary.Links[0]); // now remove the link explicitly Assert.IsTrue(mSession.RemoveLink(user, link, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Links.Count); + Assert.IsEmpty(ms.GlobalBoundary.Links); }); } @@ -89,24 +89,24 @@ public void RemoveLinkWithBadUser() { var ms = mSession.ModelSystem; CommandError error = null; - Assert.AreEqual(0, ms.GlobalBoundary.Modules.Count); + Assert.IsEmpty(ms.GlobalBoundary.Modules); Assert.IsTrue(mSession.AddNode(user, ms.GlobalBoundary, "Start", typeof(BasicParameter), Rectangle.Hidden, out var parameter, out error), error?.Message); Assert.IsTrue(mSession.AddNode(user, ms.GlobalBoundary, "Start", typeof(SimpleParameterModule), Rectangle.Hidden, out var module, out error), error?.Message); - Assert.AreEqual(2, ms.GlobalBoundary.Modules.Count); - Assert.AreEqual(0, ms.GlobalBoundary.Links.Count); + Assert.HasCount(2, ms.GlobalBoundary.Modules); + Assert.IsEmpty(ms.GlobalBoundary.Links); Assert.IsTrue(mSession.AddLink(user, module, module.Hooks[0], parameter, out var link, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Links.Count); + Assert.HasCount(1, ms.GlobalBoundary.Links); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Links.Count); + Assert.IsEmpty(ms.GlobalBoundary.Links); Assert.IsTrue(mSession.Redo(user, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Links.Count); + Assert.HasCount(1, ms.GlobalBoundary.Links); Assert.AreSame(link, ms.GlobalBoundary.Links[0]); // now remove the link explicitly Assert.IsFalse(mSession.RemoveLink(unauthorizedUser, link, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Links.Count); + Assert.HasCount(1, ms.GlobalBoundary.Links); }); } @@ -117,29 +117,29 @@ public void UndoRemoveLink() { var ms = mSession.ModelSystem; CommandError error = null; - Assert.AreEqual(0, ms.GlobalBoundary.Modules.Count); + Assert.IsEmpty(ms.GlobalBoundary.Modules); Assert.IsTrue(mSession.AddNode(user, ms.GlobalBoundary, "Start", typeof(BasicParameter), Rectangle.Hidden, out var parameter, out error), error?.Message); Assert.IsTrue(mSession.AddNode(user, ms.GlobalBoundary, "Start", typeof(SimpleParameterModule), Rectangle.Hidden, out var module, out error), error?.Message); - Assert.AreEqual(2, ms.GlobalBoundary.Modules.Count); - Assert.AreEqual(0, ms.GlobalBoundary.Links.Count); + Assert.HasCount(2, ms.GlobalBoundary.Modules); + Assert.IsEmpty(ms.GlobalBoundary.Links); Assert.IsTrue(mSession.AddLink(user, module, module.Hooks[0], parameter, out var link, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Links.Count); + Assert.HasCount(1, ms.GlobalBoundary.Links); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Links.Count); + Assert.IsEmpty(ms.GlobalBoundary.Links); Assert.IsTrue(mSession.Redo(user, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Links.Count); + Assert.HasCount(1, ms.GlobalBoundary.Links); Assert.AreSame(link, ms.GlobalBoundary.Links[0]); // now remove the link explicitly Assert.IsTrue(mSession.RemoveLink(user, link, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Links.Count); + Assert.IsEmpty(ms.GlobalBoundary.Links); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Links.Count); + Assert.HasCount(1, ms.GlobalBoundary.Links); Assert.AreSame(link, ms.GlobalBoundary.Links[0]); Assert.IsTrue(mSession.Redo(user, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Links.Count); + Assert.IsEmpty(ms.GlobalBoundary.Links); }); } @@ -159,10 +159,10 @@ public void AddSingleLinkToDifferentModule() Assert.IsTrue(mSession.AddNode(user, ms.GlobalBoundary, "MyMSS", typeof(SimpleTestModule), Rectangle.Hidden, out var mss1, out error)); Assert.IsTrue(mSession.AddNode(user, ms.GlobalBoundary, "MyMSS", typeof(SimpleTestModule), Rectangle.Hidden, out var mss2, out error)); Assert.IsTrue(mSession.AddLink(user, start, start.Hooks[0], mss1, out var link1, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Links.Count); + Assert.HasCount(1, ms.GlobalBoundary.Links); // This should not create a new link but move the previous one Assert.IsTrue(mSession.AddLink(user, start, start.Hooks[0], mss2, out var link2, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Links.Count); + Assert.HasCount(1, ms.GlobalBoundary.Links); }); } @@ -180,17 +180,17 @@ public void RemoveLinkToBoundariesThatWereRemoved() Assert.IsTrue(mSession.AddNode(user, toRemove, "Tricky", typeof(IgnoreResult), Rectangle.Hidden, out var tricky, out error), error?.Message); Assert.IsTrue(mSession.AddLink(user, start, start.Hooks[0], tricky, out var link, out error), error?.Message); - Assert.AreEqual(1, global.Starts.Count); - Assert.AreEqual(1, global.Links.Count); - Assert.AreEqual(1, toRemove.Modules.Count); + Assert.HasCount(1, global.Starts); + Assert.HasCount(1, global.Links); + Assert.HasCount(1, toRemove.Modules); // Now remove the boundary and check to make sure the number of links is cleaned up Assert.IsTrue(mSession.RemoveBoundary(user, global, toRemove, out error), error?.Message); - Assert.AreEqual(0, global.Links.Count, "We did not remove the link during the remove boundary!"); + Assert.IsEmpty(global.Links, "We did not remove the link during the remove boundary!"); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(1, global.Links.Count, "The link was not restored after the undo on the remove boundary!"); + Assert.HasCount(1, global.Links, "The link was not restored after the undo on the remove boundary!"); Assert.IsTrue(mSession.Redo(user, out error), error?.Message); - Assert.AreEqual(0, global.Links.Count, "We did not remove the link again doing the redo of the remove boundary!"); + Assert.IsEmpty(global.Links, "We did not remove the link again doing the redo of the remove boundary!"); }); } @@ -211,18 +211,18 @@ public void RemoveMultiLinkToBoundariesThatWereRemoved() out var tricky, out error), error?.Message); Assert.IsTrue(mSession.AddLink(user, start, start.Hooks[0], execute, out var link, out error), error?.Message); Assert.IsTrue(mSession.AddLink(user, execute, TestHelper.GetHook(execute.Hooks, "To Execute"), tricky, out var link2, out error), error?.Message); - Assert.AreEqual(1, global.Starts.Count); - Assert.AreEqual(1, global.Modules.Count); - Assert.AreEqual(2, global.Links.Count); - Assert.AreEqual(1, toRemove.Modules.Count); + Assert.HasCount(1, global.Starts); + Assert.HasCount(1, global.Modules); + Assert.HasCount(2, global.Links); + Assert.HasCount(1, toRemove.Modules); // Now remove the boundary and check to make sure the number of links is cleaned up Assert.IsTrue(mSession.RemoveBoundary(user, global, toRemove, out error), error?.Message); - Assert.AreEqual(0, ((MultiLink)global.Links.First(l => l.Origin == execute)).Destinations.Count); + Assert.IsEmpty(((MultiLink)global.Links.First(l => l.Origin == execute)).Destinations); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(1, ((MultiLink)global.Links.First(l => l.Origin == execute)).Destinations.Count); + Assert.HasCount(1, ((MultiLink)global.Links.First(l => l.Origin == execute)).Destinations); Assert.IsTrue(mSession.Redo(user, out error), error?.Message); - Assert.AreEqual(0, ((MultiLink)global.Links.First(l => l.Origin == execute)).Destinations.Count); + Assert.IsEmpty(((MultiLink)global.Links.First(l => l.Origin == execute)).Destinations); }); } @@ -247,13 +247,13 @@ public void RemoveSingleDestinationInMultiLink() Assert.IsTrue(mSession.AddLink(user, execute, TestHelper.GetHook(execute.Hooks, "To Execute"), execute, out var _, out error), error?.Message); - Assert.AreEqual(2, ((MultiLink)linkI1).Destinations.Count); + Assert.HasCount(2, ((MultiLink)linkI1).Destinations); Assert.IsTrue(mSession.RemoveLinkDestination(user, linkI1, 0, out error), error?.Message); - Assert.AreEqual(1, ((MultiLink)linkI1).Destinations.Count); + Assert.HasCount(1, ((MultiLink)linkI1).Destinations); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(2, ((MultiLink)linkI1).Destinations.Count); + Assert.HasCount(2, ((MultiLink)linkI1).Destinations); Assert.IsTrue(mSession.Redo(user, out error), error?.Message); - Assert.AreEqual(1, ((MultiLink)linkI1).Destinations.Count); + Assert.HasCount(1, ((MultiLink)linkI1).Destinations); }); } @@ -278,9 +278,9 @@ public void RemoveSingleDestinationInMultiLinkWithBadUser() Assert.IsTrue(mSession.AddLink(user, execute, TestHelper.GetHook(execute.Hooks, "To Execute"), execute, out var _, out error), error?.Message); - Assert.AreEqual(2, ((MultiLink)linkI1).Destinations.Count); + Assert.HasCount(2, ((MultiLink)linkI1).Destinations); Assert.IsFalse(mSession.RemoveLinkDestination(unauthorizedUser, linkI1, 0, out error), error?.Message); - Assert.AreEqual(2, ((MultiLink)linkI1).Destinations.Count, "An unauthorized user was able to change the number of destinations."); + Assert.HasCount(2, ((MultiLink)linkI1).Destinations, "An unauthorized user was able to change the number of destinations."); }); } @@ -311,8 +311,8 @@ public void DisableLink() var ms = mSession.ModelSystem; var modules = ms.GlobalBoundary.Modules; var links = ms.GlobalBoundary.Links; - Assert.AreEqual(3, modules.Count); - Assert.AreEqual(1, links.Count); + Assert.HasCount(3, modules); + Assert.HasCount(1, links); Assert.IsTrue(links[0].IsDisabled, "The link was not disabled on reload."); }); } diff --git a/tests/XTMF2.UnitTests/Editing/TestNode.cs b/tests/XTMF2.UnitTests/Editing/TestNode.cs index f7fccd8..5a41460 100644 --- a/tests/XTMF2.UnitTests/Editing/TestNode.cs +++ b/tests/XTMF2.UnitTests/Editing/TestNode.cs @@ -36,10 +36,10 @@ public void AddStart() { var ms = mSession.ModelSystem; CommandError error = null; - Assert.AreEqual(0, ms.GlobalBoundary.Starts.Count); + Assert.IsEmpty(ms.GlobalBoundary.Starts); Assert.IsTrue(mSession.AddModelSystemStart(user, ms.GlobalBoundary, "Start", Rectangle.Hidden, out var Start, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Starts.Count); + Assert.HasCount(1, ms.GlobalBoundary.Starts); }); } @@ -50,10 +50,10 @@ public void AddStartWithBadUser() { var ms = mSession.ModelSystem; CommandError error = null; - Assert.AreEqual(0, ms.GlobalBoundary.Starts.Count); + Assert.IsEmpty(ms.GlobalBoundary.Starts); Assert.IsFalse(mSession.AddModelSystemStart(unauthorizedUser, ms.GlobalBoundary, "Start", Rectangle.Hidden, out var Start, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Starts.Count); + Assert.IsEmpty(ms.GlobalBoundary.Starts); }); } @@ -64,14 +64,14 @@ public void UndoAddStart() { var ms = mSession.ModelSystem; CommandError error = null; - Assert.AreEqual(0, ms.GlobalBoundary.Starts.Count); + Assert.IsEmpty(ms.GlobalBoundary.Starts); Assert.IsTrue(mSession.AddModelSystemStart(user, ms.GlobalBoundary, "Start", Rectangle.Hidden, out var Start, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Starts.Count); + Assert.HasCount(1, ms.GlobalBoundary.Starts); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Starts.Count); + Assert.IsEmpty(ms.GlobalBoundary.Starts); Assert.IsTrue(mSession.Redo(user, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Starts.Count); + Assert.HasCount(1, ms.GlobalBoundary.Starts); Assert.AreSame(Start, ms.GlobalBoundary.Starts[0]); }); } @@ -83,19 +83,19 @@ public void RemoveStart() { var ms = mSession.ModelSystem; CommandError error = null; - Assert.AreEqual(0, ms.GlobalBoundary.Starts.Count); + Assert.IsEmpty(ms.GlobalBoundary.Starts); Assert.IsTrue(mSession.AddModelSystemStart(user, ms.GlobalBoundary, "Start", Rectangle.Hidden, out var Start, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Starts.Count); + Assert.HasCount(1, ms.GlobalBoundary.Starts); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Starts.Count); + Assert.IsEmpty(ms.GlobalBoundary.Starts); Assert.IsTrue(mSession.Redo(user, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Starts.Count); + Assert.HasCount(1, ms.GlobalBoundary.Starts); Assert.AreSame(Start, ms.GlobalBoundary.Starts[0]); //now test explicitly removing the start Assert.IsTrue(mSession.RemoveStart(user, Start, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Starts.Count); + Assert.IsEmpty(ms.GlobalBoundary.Starts); }); } @@ -106,19 +106,19 @@ public void RemoveStartWithBadUser() { var ms = mSession.ModelSystem; CommandError error = null; - Assert.AreEqual(0, ms.GlobalBoundary.Starts.Count); + Assert.IsEmpty(ms.GlobalBoundary.Starts); Assert.IsTrue(mSession.AddModelSystemStart(user, ms.GlobalBoundary, "Start", Rectangle.Hidden, out var Start, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Starts.Count); + Assert.HasCount(1, ms.GlobalBoundary.Starts); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Starts.Count); + Assert.IsEmpty(ms.GlobalBoundary.Starts); Assert.IsTrue(mSession.Redo(user, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Starts.Count); + Assert.HasCount(1, ms.GlobalBoundary.Starts); Assert.AreSame(Start, ms.GlobalBoundary.Starts[0]); //now test explicitly removing the start Assert.IsFalse(mSession.RemoveStart(unauthorizedUser, Start, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Starts.Count); + Assert.HasCount(1, ms.GlobalBoundary.Starts); }); } @@ -129,22 +129,22 @@ public void UndoRemoveStart() { var ms = mSession.ModelSystem; CommandError error = null; - Assert.AreEqual(0, ms.GlobalBoundary.Starts.Count); + Assert.IsEmpty(ms.GlobalBoundary.Starts); Assert.IsTrue(mSession.AddModelSystemStart(user, ms.GlobalBoundary, "Start", Rectangle.Hidden, out var Start, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Starts.Count); + Assert.HasCount(1, ms.GlobalBoundary.Starts); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Starts.Count); + Assert.IsEmpty(ms.GlobalBoundary.Starts); Assert.IsTrue(mSession.Redo(user, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Starts.Count); + Assert.HasCount(1, ms.GlobalBoundary.Starts); Assert.AreSame(Start, ms.GlobalBoundary.Starts[0]); //now test explicitly removing the start Assert.IsTrue(mSession.RemoveStart(user, Start, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Starts.Count); + Assert.IsEmpty(ms.GlobalBoundary.Starts); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Starts.Count); + Assert.HasCount(1, ms.GlobalBoundary.Starts); Assert.IsTrue(mSession.Redo(user, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Starts.Count); + Assert.IsEmpty(ms.GlobalBoundary.Starts); }); } @@ -155,10 +155,10 @@ public void AddNode() { var ms = mSession.ModelSystem; CommandError error = null; - Assert.AreEqual(0, ms.GlobalBoundary.Modules.Count); + Assert.IsEmpty(ms.GlobalBoundary.Modules); Assert.IsTrue(mSession.AddNode(user, ms.GlobalBoundary, "Start", typeof(BasicParameter), Rectangle.Hidden, out var mss, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Modules.Count); + Assert.HasCount(1, ms.GlobalBoundary.Modules); }); } @@ -169,10 +169,10 @@ public void AddNodeWithBadUser() { var ms = mSession.ModelSystem; CommandError error = null; - Assert.AreEqual(0, ms.GlobalBoundary.Modules.Count); + Assert.IsEmpty(ms.GlobalBoundary.Modules); Assert.IsFalse(mSession.AddNode(unauthorizedUser, ms.GlobalBoundary, "Start", typeof(BasicParameter), Rectangle.Hidden, out var mss, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Modules.Count); + Assert.IsEmpty(ms.GlobalBoundary.Modules); }); } @@ -183,14 +183,14 @@ public void UndoAddNode() { var ms = mSession.ModelSystem; CommandError error = null; - Assert.AreEqual(0, ms.GlobalBoundary.Modules.Count); + Assert.IsEmpty(ms.GlobalBoundary.Modules); Assert.IsTrue(mSession.AddNode(user, ms.GlobalBoundary, "Start", typeof(BasicParameter), Rectangle.Hidden, out var mss, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Modules.Count); + Assert.HasCount(1, ms.GlobalBoundary.Modules); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Modules.Count); + Assert.IsEmpty(ms.GlobalBoundary.Modules); Assert.IsTrue(mSession.Redo(user, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Modules.Count); + Assert.HasCount(1, ms.GlobalBoundary.Modules); Assert.AreSame(mss, ms.GlobalBoundary.Modules[0]); }); } @@ -202,19 +202,19 @@ public void RemoveNode() { var ms = mSession.ModelSystem; CommandError error = null; - Assert.AreEqual(0, ms.GlobalBoundary.Modules.Count); + Assert.IsEmpty(ms.GlobalBoundary.Modules); Assert.IsTrue(mSession.AddNode(user, ms.GlobalBoundary, "Start", typeof(BasicParameter), Rectangle.Hidden, out var mss, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Modules.Count); + Assert.HasCount(1, ms.GlobalBoundary.Modules); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Modules.Count); + Assert.IsEmpty(ms.GlobalBoundary.Modules); Assert.IsTrue(mSession.Redo(user, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Modules.Count); + Assert.HasCount(1, ms.GlobalBoundary.Modules); Assert.AreSame(mss, ms.GlobalBoundary.Modules[0]); // now remove node explicitly Assert.IsTrue(mSession.RemoveNode(user, mss, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Modules.Count); + Assert.IsEmpty(ms.GlobalBoundary.Modules); }); } @@ -225,19 +225,19 @@ public void RemoveNodeWithBadUser() { var ms = mSession.ModelSystem; CommandError error = null; - Assert.AreEqual(0, ms.GlobalBoundary.Modules.Count); + Assert.IsEmpty(ms.GlobalBoundary.Modules); Assert.IsTrue(mSession.AddNode(user, ms.GlobalBoundary, "Start", typeof(BasicParameter), Rectangle.Hidden, out var mss, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Modules.Count); + Assert.HasCount(1, ms.GlobalBoundary.Modules); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Modules.Count); + Assert.IsEmpty(ms.GlobalBoundary.Modules); Assert.IsTrue(mSession.Redo(user, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Modules.Count); + Assert.HasCount(1, ms.GlobalBoundary.Modules); Assert.AreSame(mss, ms.GlobalBoundary.Modules[0]); // now remove node explicitly Assert.IsFalse(mSession.RemoveNode(unauthorizedUser, mss, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Modules.Count); + Assert.HasCount(1, ms.GlobalBoundary.Modules); }); } @@ -248,21 +248,21 @@ public void UndoRemoveNode() { var ms = mSession.ModelSystem; CommandError error = null; - Assert.AreEqual(0, ms.GlobalBoundary.Modules.Count); + Assert.IsEmpty(ms.GlobalBoundary.Modules); Assert.IsTrue(mSession.AddNode(user, ms.GlobalBoundary, "Start", typeof(BasicParameter), Rectangle.Hidden, out var mss, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Modules.Count); + Assert.HasCount(1, ms.GlobalBoundary.Modules); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Modules.Count); + Assert.IsEmpty(ms.GlobalBoundary.Modules); Assert.IsTrue(mSession.Redo(user, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Modules.Count); + Assert.HasCount(1, ms.GlobalBoundary.Modules); Assert.AreSame(mss, ms.GlobalBoundary.Modules[0]); // now remove node explicitly Assert.IsTrue(mSession.RemoveNode(user, mss, out error), error?.Message); - Assert.AreEqual(0, ms.GlobalBoundary.Modules.Count); + Assert.IsEmpty(ms.GlobalBoundary.Modules); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); - Assert.AreEqual(1, ms.GlobalBoundary.Modules.Count); + Assert.HasCount(1, ms.GlobalBoundary.Modules); Assert.IsTrue(mSession.Undo(user, out error), error?.Message); }); } @@ -282,11 +282,11 @@ public void AddNodeWithParameterGeneration() typeof(SimpleParameterModule), Rectangle.Hidden, out var node, out var children, out error), error?.Message); // Test to make sure that there was a second module also added. Assert.IsNotNull(children, "The child parameters of the node were returned as a null!"); - Assert.AreEqual(1, children.Count); + Assert.HasCount(1, children); var modules = gBound.Modules; var links = gBound.Links; - Assert.AreEqual(2, modules.Count, "It seems that the child parameter was not contained in the global boundary."); - Assert.AreEqual(1, links.Count, "We did not have a link!"); + Assert.HasCount(2, modules, "It seems that the child parameter was not contained in the global boundary."); + Assert.HasCount(1, links, "We did not have a link!"); // Find the automatically added basic parameter and make sure that it has the correct default value bool found = false; for (int i = 0; i < modules.Count; i++) @@ -295,7 +295,7 @@ public void AddNodeWithParameterGeneration() { found = true; Assert.AreEqual(typeof(BasicParameter), modules[i].Type, "The automatically generated parameter was not of type BasicParameter!"); - Assert.AreEqual("Hello World", modules[i].ParameterValue, "The default value of the parameter was not 'Hello World'!"); + Assert.AreEqual("Hello World", modules[i].ParameterValue.Representation, "The default value of the parameter was not 'Hello World'!"); break; } } @@ -320,8 +320,8 @@ public void AddNodeWithParameterGenerationWithBadUser() Assert.IsNull(children, "The child parameters of the node were returned as a null!"); var modules = gBound.Modules; var links = gBound.Links; - Assert.AreEqual(0, modules.Count, "A module was created by an invalid user!"); - Assert.AreEqual(0, links.Count, "A link was created by an invalid user!"); + Assert.IsEmpty(modules, "A module was created by an invalid user!"); + Assert.IsEmpty(links, "A link was created by an invalid user!"); }); } @@ -341,17 +341,17 @@ public void AddNodeWithParameterGenerationUndo() typeof(SimpleParameterModule), Rectangle.Hidden, out var node, out var children, out error), error?.Message); // Test to make sure that there was a second module also added. Assert.IsNotNull(children, "The child parameters of the node were returned as a null!"); - Assert.AreEqual(1, children.Count); + Assert.HasCount(1, children); var modules = gBound.Modules; var links = gBound.Links; - Assert.AreEqual(2, modules.Count, "It seems that the child parameter was not contained in the global boundary."); - Assert.AreEqual(1, links.Count, "We did not have a link!"); + Assert.HasCount(2, modules, "It seems that the child parameter was not contained in the global boundary."); + Assert.HasCount(1, links, "We did not have a link!"); Assert.IsTrue(msSession.Undo(user, out error), error?.Message); - Assert.AreEqual(0, modules.Count, "After undoing it seems that a module has survived."); - Assert.AreEqual(0, links.Count, "The link was not removed on undo."); + Assert.IsEmpty(modules, "After undoing it seems that a module has survived."); + Assert.IsEmpty(links, "The link was not removed on undo."); Assert.IsTrue(msSession.Redo(user, out error), error?.Message); - Assert.AreEqual(2, modules.Count, "After redoing it seems that a module was not restored."); - Assert.AreEqual(1, links.Count, "The link was not re-added on redo."); + Assert.HasCount(2, modules, "After redoing it seems that a module was not restored."); + Assert.HasCount(1, links, "The link was not re-added on redo."); }); } @@ -370,11 +370,11 @@ public void RemoveNodeWithParameterGeneration() typeof(SimpleParameterModule), Rectangle.Hidden, out var node, out var children, out error), error?.Message); // Test to make sure that there was a second module also added. Assert.IsNotNull(children, "The child parameters of the node were returned as a null!"); - Assert.AreEqual(1, children.Count); + Assert.HasCount(1, children); var modules = gBound.Modules; var links = gBound.Links; - Assert.AreEqual(2, modules.Count, "It seems that the child parameter was not contained in the global boundary."); - Assert.AreEqual(1, links.Count, "We did not have a link!"); + Assert.HasCount(2, modules, "It seems that the child parameter was not contained in the global boundary."); + Assert.HasCount(1, links, "We did not have a link!"); // Find the automatically added basic parameter and make sure that it has the correct default value bool found = false; for (int i = 0; i < modules.Count; i++) @@ -383,7 +383,7 @@ public void RemoveNodeWithParameterGeneration() { found = true; Assert.AreEqual(typeof(BasicParameter), modules[i].Type, "The automatically generated parameter was not of type BasicParameter!"); - Assert.AreEqual("Hello World", modules[i].ParameterValue, "The default value of the parameter was not 'Hello World'!"); + Assert.AreEqual("Hello World", modules[i].ParameterValue.Representation, "The default value of the parameter was not 'Hello World'!"); break; } } @@ -392,12 +392,12 @@ public void RemoveNodeWithParameterGeneration() Assert.IsTrue(msSession.RemoveNodeGenerateParameters(user, node, out error), error?.Message); // Make sure that both modules were deleted - Assert.AreEqual(0, modules.Count, "Both modules were not removed."); + Assert.IsEmpty(modules, "Both modules were not removed."); Assert.IsTrue(msSession.Undo(user, out error), error?.Message); - Assert.AreEqual(2, modules.Count, "Both modules were not re-added."); + Assert.HasCount(2, modules, "Both modules were not re-added."); Assert.IsTrue(msSession.Redo(user, out error), error?.Message); - Assert.AreEqual(0, modules.Count, "Both modules were not removed again."); + Assert.IsEmpty(modules, "Both modules were not removed again."); }); } @@ -416,11 +416,11 @@ public void RemoveNodeWithParameterGenerationWithBadUser() typeof(SimpleParameterModule), Rectangle.Hidden, out var node, out var children, out error), error?.Message); // Test to make sure that there was a second module also added. Assert.IsNotNull(children, "The child parameters of the node were returned as a null!"); - Assert.AreEqual(1, children.Count); + Assert.HasCount(1, children); var modules = gBound.Modules; var links = gBound.Links; - Assert.AreEqual(2, modules.Count, "It seems that the child parameter was not contained in the global boundary."); - Assert.AreEqual(1, links.Count, "We did not have a link!"); + Assert.HasCount(2, modules, "It seems that the child parameter was not contained in the global boundary."); + Assert.HasCount(1, links, "We did not have a link!"); // Find the automatically added basic parameter and make sure that it has the correct default value bool found = false; for (int i = 0; i < modules.Count; i++) @@ -429,14 +429,14 @@ public void RemoveNodeWithParameterGenerationWithBadUser() { found = true; Assert.AreEqual(typeof(BasicParameter), modules[i].Type, "The automatically generated parameter was not of type BasicParameter!"); - Assert.AreEqual("Hello World", modules[i].ParameterValue, "The default value of the parameter was not 'Hello World'!"); + Assert.AreEqual("Hello World", modules[i].ParameterValue.Representation, "The default value of the parameter was not 'Hello World'!"); break; } } Assert.IsTrue(found, "We did not find the automatically created parameter module!"); Assert.IsFalse(msSession.RemoveNodeGenerateParameters(unauthorizedUser, node, out error), error?.Message); - Assert.AreEqual(2, modules.Count, "The number of modules changed after an invalid user invoked RemoveNodeGenerateParameters."); - Assert.AreEqual(1, links.Count, "The number of links changed after an invalid user invoked RemoveNodeGenerateParameters!"); + Assert.HasCount(2, modules, "The number of modules changed after an invalid user invoked RemoveNodeGenerateParameters."); + Assert.HasCount(1, links, "The number of links changed after an invalid user invoked RemoveNodeGenerateParameters!"); }); } @@ -459,22 +459,22 @@ public void RemoveNodeWithParameterGenerationNotRemovingIfMultiple() typeof(SimpleParameterModule), Rectangle.Hidden, out var node2, out error), error?.Message); // Test to make sure that there was a second module also added. Assert.IsNotNull(children, "The child parameters of the node were returned as a null!"); - Assert.AreEqual(1, children.Count); + Assert.HasCount(1, children); var modules = gBound.Modules; var links = gBound.Links; - Assert.AreEqual(1, links.Count); - Assert.AreEqual(3, modules.Count); + Assert.HasCount(1, links); + Assert.HasCount(3, modules); Assert.IsTrue(msSession.AddLink(user, node2, node2.Hooks[0], children[0], out var node2Link, out error), error?.Message); - Assert.AreEqual(2, links.Count, "The second link was not added"); + Assert.HasCount(2, links, "The second link was not added"); Assert.IsTrue(msSession.RemoveNodeGenerateParameters(user, node, out error), error?.Message); - Assert.AreEqual(1, links.Count); - Assert.AreEqual(2, modules.Count); + Assert.HasCount(1, links); + Assert.HasCount(2, modules); Assert.IsTrue(msSession.Undo(user, out error), error?.Message); - Assert.AreEqual(2, links.Count); - Assert.AreEqual(3, modules.Count); + Assert.HasCount(2, links); + Assert.HasCount(3, modules); Assert.IsTrue(msSession.Redo(user, out error), error?.Message); - Assert.AreEqual(1, links.Count); - Assert.AreEqual(2, modules.Count); + Assert.HasCount(1, links); + Assert.HasCount(2, modules); }); } @@ -495,7 +495,7 @@ public void SetParameterValue() Assert.IsTrue(msSession.AddNode(user, ms.GlobalBoundary, "MyParameter", typeof(BasicParameter), Rectangle.Hidden, out var basicParameter, out error2), error2?.Message); Assert.IsTrue(msSession.SetParameterValue(user, basicParameter, parameterValue, out error2), error2?.Message); - Assert.AreEqual(parameterValue, basicParameter.ParameterValue, "The value of the parameter was not set correctly."); + Assert.AreEqual(parameterValue, basicParameter.ParameterValue.Representation, "The value of the parameter was not set correctly."); }); } @@ -517,10 +517,10 @@ public void SetParameterValueWithBadUser() Assert.IsTrue(msSession.AddNode(user, ms.GlobalBoundary, "MyParameter", typeof(BasicParameter), Rectangle.Hidden, out var basicParameter, out error2), error2?.Message); Assert.IsTrue(msSession.SetParameterValue(user, basicParameter, parameterValue, out error2), error2?.Message); - Assert.AreEqual(parameterValue, basicParameter.ParameterValue, "The value of the parameter was not set correctly."); + Assert.AreEqual(parameterValue, basicParameter.ParameterValue.Representation, "The value of the parameter was not set correctly."); Assert.IsFalse(msSession.SetParameterValue(unauthorizedUser, basicParameter, badParameterValue, out error2), error2?.Message); - Assert.AreEqual(parameterValue, basicParameter.ParameterValue, "The unauthorized user changed the parameter's value!"); + Assert.AreEqual(parameterValue, basicParameter.ParameterValue.Representation, "The unauthorized user changed the parameter's value!"); }); } @@ -546,7 +546,7 @@ public void DisablingNode() // after shutdown var ms = mSession.ModelSystem; var modules = ms.GlobalBoundary.Modules; - Assert.AreEqual(1, modules.Count); + Assert.HasCount(1, modules); Assert.IsTrue(modules[0].IsDisabled, "The module was not disabled after reloading the model system!"); }); } diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAddOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAddOperator.cs new file mode 100644 index 0000000..7f1cb1a --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAddOperator.cs @@ -0,0 +1,187 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using XTMF2.ModelSystemConstruct; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; + +using static XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler.TestCompilerHelper; + +namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; + +[TestClass] +public class TestAddOperator +{ + [TestMethod] + public void TestAddInteger() + { + TestExpression("123 + 312", 435); + } + + [TestMethod] + public void TestAddFloat() + { + TestExpression("123.0 + 312.0", 435.0f); + } + + [TestMethod] + public void TestAddString() + { + TestExpression("\"1\" + \"2\"", "12"); + } + + [TestMethod] + public void TestAddBoolean() + { + TestFails("true + false"); + } + + [TestMethod] + public void TestMultipleAddsInteger() + { + TestExpression("1 + 2 + 3", 6); + } + + [TestMethod] + public void TestAddMultipleString() + { + TestExpression("\"1\" + \"2\" + \"3\"", "123"); + } + + [TestMethod] + public void TestMultipleAddsFloat() + { + TestExpression("1.0 + 2.0 + 3.0", 6.0f); + } + + [TestMethod] + public void TestMultipleAddsBoolean() + { + TestFails("true + false true"); + } + + [TestMethod] + public void TestMultipleAddsSpacesOnSides() + { + TestExpression(" 1 + 2 + 3 ", 6); + } + + [TestMethod] + public void TestAddExtraOnLeftFails() + { + TestFails("asd 1 + 2"); + } + + [TestMethod] + public void TestAddExtraOnRightFails() + { + TestFails("1 + 2 asd"); + } + + [TestMethod] + public void TestAddAtEndFails() + { + TestFails("1 +"); + } + + [TestMethod] + public void TestAddAtStartFails() + { + TestFails("+ 1"); + } + + [TestMethod] + public void TestAddInString() + { + TestExpression("\"1+2\"", "1+2"); + } + + [TestMethod] + public void TestMixedIntFloatAdd() + { + TestFails("1.0 + 1"); + } + + [TestMethod] + public void TestMixedIntStrAdd() + { + TestExpression("1 + \"Hello\"", "1Hello"); + } + + [TestMethod] + public void TestMixedIntBooleanAdd() + { + TestFails("1 + true"); + } + + + [TestMethod] + public void TestMixedFloatIntAdd() + { + TestFails("1.0 + 1"); + } + + [TestMethod] + public void TestMixedFloatStrAdd() + { + TestExpression("1.2 + \"Hello\"", "1.2Hello"); + } + + [TestMethod] + public void TestMixedFloatBooleanAdd() + { + TestFails("1.0 + true"); + } + + [TestMethod] + public void TestMixedStrIntAdd() + { + TestExpression("\"1\" + 1", "11"); + } + + [TestMethod] + public void TestMixedStrFloatAdd() + { + TestExpression("\"1\" + 1.1", "11.1"); + } + + [TestMethod] + public void TestMixedStrBooleanAdd() + { + TestExpression("\"1.0\" + true","1.0True"); + } + + [TestMethod] + public void TestMixedBooleanIntAdd() + { + TestFails("true + 1"); + } + + [TestMethod] + public void TestMixedBooleanFloatAdd() + { + TestFails("true + 1.0"); + } + + [TestMethod] + public void TestMixedBooleanStrAdd() + { + TestExpression("true + \"true\"", "Truetrue"); + } +} diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAndOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAndOperator.cs new file mode 100644 index 0000000..7be2a6f --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAndOperator.cs @@ -0,0 +1,149 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using XTMF2.ModelSystemConstruct; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; + +using static XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler.TestCompilerHelper; +namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; + +[TestClass] +public class TestAndOperator +{ + [TestMethod] + public void TestAnd() + { + TestExpression("true && true", true); + TestExpression("true && false", false); + TestExpression("false && true", false); + TestExpression("false && false", false); + } + + [TestMethod] + public void TestAndNoSpaces() + { + TestExpression("true&&true", true); + TestExpression("true&&false", false); + TestExpression("false&&true", false); + TestExpression("false&&false", false); + } + + [TestMethod] + public void TestAndInString() + { + TestExpression("\"true && true\"", "true && true"); + } + + [TestMethod] + public void TestAndWithIntegers() + { + TestFails("1&&2"); + } + + [TestMethod] + public void TestAndWithFloats() + { + TestFails("1.0&&2.0"); + } + + [TestMethod] + public void TestAndWithStrings() + { + TestFails("\"1.0\"&&\"2.0\""); + } + + [TestMethod] + public void TestAndOutsideOfCompare() + { + TestExpression("true && false == false", true); + } + + [TestMethod] + public void TestMixedIntFloatAnd() + { + TestFails("1.0 && 1"); + } + + [TestMethod] + public void TestMixedIntStrAnd() + { + TestFails("1 && \"Hello\""); + } + + [TestMethod] + public void TestMixedIntBooleanAnd() + { + TestFails("1 && true"); + } + + [TestMethod] + public void TestMixedFloatIntAnd() + { + TestFails("1.0 && 1"); + } + + [TestMethod] + public void TestMixedFloatStrAnd() + { + TestFails("1.0 && \"Hello\""); + } + + [TestMethod] + public void TestMixedFloatBooleanAnd() + { + TestFails("1.0 && true"); + } + + [TestMethod] + public void TestMixedStrIntAnd() + { + TestFails("\"1\" && 1"); + } + + [TestMethod] + public void TestMixedStrFloatAnd() + { + TestFails("\"1\" && 1.0"); + } + + [TestMethod] + public void TestMixedStrBooleanAnd() + { + TestFails("\"1.0\" && true"); + } + + [TestMethod] + public void TestMixedBooleanIntAnd() + { + TestFails("true && 1"); + } + + [TestMethod] + public void TestMixedBooleanFloatAnd() + { + TestFails("true && 1.0"); + } + + [TestMethod] + public void TestMixedBooleanStrAnd() + { + TestFails("true && \"true\""); + } +} diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestBracket.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestBracket.cs new file mode 100644 index 0000000..d980ae5 --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestBracket.cs @@ -0,0 +1,106 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using XTMF2.ModelSystemConstruct; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; + +using static XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler.TestCompilerHelper; + +namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; + +[TestClass] +public class TestBracket +{ + /// + /// Gives an empty set of nodes for use in the compiler. + /// + private static readonly List EmptyNodeList = new(); + + [TestMethod] + public void TestBracketIntegerLiteral() + { + TestExpression("(12345)", 12345); + } + + [TestMethod] + public void TestWhitespaceBeforeBracketIntegerLiteral() + { + TestExpression(" (12345)", 12345); + } + + [TestMethod] + public void TestWhitespaceAfterBracketIntegerLiteral() + { + TestExpression("(12345) ", 12345); + } + + [TestMethod] + public void TestDoubleBracketIntegerLiteral() + { + TestExpression("((12345))", 12345); + } + + [TestMethod] + public void TestTooManyOpenBrackets() + { + TestFails("((12345)"); + } + + [TestMethod] + public void TestTooManyCloseBrackets() + { + TestFails("(12345))"); + } + + [TestMethod] + public void TestTextBeforeBracket() + { + TestFails("asd (12345)"); + } + + [TestMethod] + public void TestTextAfterBracket() + { + TestFails("(12345) asd"); + } + + [TestMethod] + public void TestCloseBracketFirst() + { + TestFails(")(12345)"); + } + [TestMethod] + public void TestCloseBracketBeforeOpen() + { + TestFails(")(12345)("); + } + + [TestMethod] + public void TestOpenBracketInString() + { + TestExpression("\"(\"", "("); + } + + [TestMethod] + public void TestCloseBracketInString() + { + TestExpression("\")\"", ")"); + } +} diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestCompilerHelper.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestCompilerHelper.cs new file mode 100644 index 0000000..916b62b --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestCompilerHelper.cs @@ -0,0 +1,107 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using XTMF2.Editing; +using XTMF2.ModelSystemConstruct; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; +using XTMF2.RuntimeModules; + +namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; + +/// +/// Provides common functionality for the unit tests for the parameter compiler +/// +internal static class TestCompilerHelper +{ + /// + /// Gives an empty set of nodes for use in the compiler. + /// + internal static readonly List EmptyNodeList = new(); + + /// + /// Provides a common call site for testing script results against an expected value. + /// + /// The text value to test. + /// The expected result of the text. + internal static void TestExpression(string text, T expectedResult, IList nodes = null) + { + string error = null; + if (nodes is null) + { + nodes = EmptyNodeList; + } + Assert.IsTrue(ParameterCompiler.CreateExpression(nodes, text, out var expression, ref error), $"Failed to compile \"{text}\"!"); + Assert.IsNotNull(expression, "The a null expression was returned!"); + Assert.AreEqual(typeof(T), expression.Type); + Assert.IsTrue(ParameterCompiler.Evaluate(null!, expression, out var result, ref error), error); + if (result is T res) + { + if (typeof(T) == typeof(float)) + { + // The object->float conversion will be optimized away by the JIT + Assert.AreEqual((float)(object)expectedResult, (float)(object)res, 0.0001f); + } + else + { + Assert.AreEqual(expectedResult, res); + } + } + else + { + Assert.Fail($"The result is not a {typeof(T).FullName}!"); + } + } + + /// + /// Provides a common call site for testing boolean results. + /// + /// The text value to test. + /// The expected result of the text. + /// The nodes to use as potential variables. + internal static void TestFails(string text, IList nodes = null) + { + string error = null; + if(nodes is null) + { + nodes = EmptyNodeList; + } + Assert.IsFalse(ParameterCompiler.CreateExpression(nodes, text, out var expression, ref error), $"Successfully compiled bad code!: {text}"); + Assert.IsNotNull(error, "There was no error message!"); + Assert.IsNull(expression, "The expression should have been null!"); + } + + /// + /// Create a new node to use for variables. + /// + /// The type of the variable to create + /// The model system session that we are working in. + /// The user that is creating the nodes. + /// The name of the node. + /// The value of the node. + /// The resulting node + internal static Node CreateNodeForVariable(ModelSystemSession session, User user, string name, string value) + { + Assert.IsTrue(session.AddNode(user, session.ModelSystem.GlobalBoundary, name, + typeof(BasicParameter), + new Rectangle(0, 0, 0, 0), out var node, out var error), error?.Message); + Assert.IsTrue(session.SetParameterValue(user, node, value, out error), error?.Message); + return node; + } +} diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestDivideOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestDivideOperator.cs new file mode 100644 index 0000000..d4172cb --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestDivideOperator.cs @@ -0,0 +1,158 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using XTMF2.ModelSystemConstruct; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; + +using static XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler.TestCompilerHelper; + +namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; + +[TestClass] +public class TestDivideOperator +{ + [TestMethod] + public void TestDivide() + { + TestExpression("123 / 312", 0); + TestExpression("123 / 122", 1); + } + + [TestMethod] + public void TestDivideFloat() + { + TestExpression("123.0 / 312.0", 0.3942307f); + TestExpression("123.0 / 122.0", 1.0081967f); + } + + [TestMethod] + public void TestDivideBool() + { + TestFails("true / false"); + } + + [TestMethod] + public void TestMultipleDivisions() + { + TestExpression("1 / 2 / 3", 0); + } + + [TestMethod] + public void TestMultipleDivisionsSpacesOnSides() + { + TestExpression(" 1 / 2 / 3 ", 0); + } + + [TestMethod] + public void TestDivideExtraOnLeftFails() + { + TestFails("asd 1 / 2"); + } + + [TestMethod] + public void TestDivideExtraOnRightFails() + { + TestFails("1 / 2 asd"); + } + + [TestMethod] + public void TestDivideAtEndFails() + { + TestFails("1 /"); + } + + [TestMethod] + public void TestDivideAtStartFails() + { + TestFails("/ 1"); + } + + [TestMethod] + public void TestMixedIntFloatDivide() + { + TestFails("1.0 / 1"); + } + + [TestMethod] + public void TestMixedIntStrDivide() + { + TestFails("1 / \"Hello\""); + } + + [TestMethod] + public void TestMixedIntBooleanDivide() + { + TestFails("1 / true"); + } + + [TestMethod] + public void TestMixedFloatIntDivide() + { + TestFails("1.0 / 1"); + } + + [TestMethod] + public void TestMixedFloatStrDivide() + { + TestFails("1.0 / \"Hello\""); + } + + [TestMethod] + public void TestMixedFloatBooleanDivide() + { + TestFails("1.0 / true"); + } + + [TestMethod] + public void TestMixedStrIntDivide() + { + TestFails("\"1\" / 1"); + } + + [TestMethod] + public void TestMixedStrFloatDivide() + { + TestFails("\"1\" / 1.0"); + } + + [TestMethod] + public void TestMixedStrBooleanDivide() + { + TestFails("\"1.0\" / true"); + } + + [TestMethod] + public void TestMixedBooleanIntDivide() + { + TestFails("true / 1"); + } + + [TestMethod] + public void TestMixedBooleanFloatDivide() + { + TestFails("true / 1.0"); + } + + [TestMethod] + public void TestMixedBooleanStrDivide() + { + TestFails("true / \"true\""); + } +} diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestEqualsOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestEqualsOperator.cs new file mode 100644 index 0000000..d9d8ced --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestEqualsOperator.cs @@ -0,0 +1,172 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using XTMF2.ModelSystemConstruct; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; + +using static XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler.TestCompilerHelper; + +namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; + +[TestClass] +public class TestEqualsOperator +{ + [TestMethod] + public void TestDifferentValue() + { + TestExpression("123 == 312", false); + } + + [TestMethod] + public void TestSameValue() + { + TestExpression("123 == 123", true); + } + + [TestMethod] + public void TestFailsTextOnLeft() + { + TestFails("asd 123 == 312"); + } + + [TestMethod] + public void TesFailsTextOnRight() + { + TestFails("123 == 312 asd"); + } + + [TestMethod] + public void TestNothingOnLeftFails() + { + TestFails("== 123"); + } + + [TestMethod] + public void TestNothingOnRightFails() + { + TestFails("123 =="); + } + + [TestMethod] + public void TestEqualsInString() + { + TestExpression("\"true==false\"", "true==false"); + } + + [TestMethod] + public void TestEqualsAfterLessThan() + { + TestExpression("123 < 312 == true", true); + TestExpression("312 < 123 == true", false); + } + + [TestMethod] + public void TestEqualsAfterLessThanOrEqualsThan() + { + TestExpression("123 <= 312 == true", true); + TestExpression("312 <= 123 == true", false); + } + + [TestMethod] + public void TestEqualsAfterGreaterThan() + { + TestExpression("123 > 312 == true", false); + TestExpression("312 > 123 == true", true); + } + + [TestMethod] + public void TestEqualsAfterGreaterThanOrEqualsThan() + { + TestExpression("123 >= 312 == true", false); + TestExpression("312 >= 123 == true", true); + } + + [TestMethod] + public void TestMixedIntFloatEquals() + { + TestFails("1.0 == 1"); + } + + [TestMethod] + public void TestMixedIntStrEquals() + { + TestFails("1 == \"Hello\""); + } + + [TestMethod] + public void TestMixedIntBooleanEquals() + { + TestFails("1 == true"); + } + + [TestMethod] + public void TestMixedFloatIntEquals() + { + TestFails("1.0 == 1"); + } + + [TestMethod] + public void TestMixedFloatStrEquals() + { + TestFails("1.0 == \"Hello\""); + } + + [TestMethod] + public void TestMixedFloatBooleanEquals() + { + TestFails("1.0 == true"); + } + + [TestMethod] + public void TestMixedStrIntEquals() + { + TestFails("\"1\" == 1"); + } + + [TestMethod] + public void TestMixedStrFloatEquals() + { + TestFails("\"1\" == 1.0"); + } + + [TestMethod] + public void TestMixedStrBooleanEquals() + { + TestFails("\"1.0\" == true"); + } + + [TestMethod] + public void TestMixedBooleanIntEquals() + { + TestFails("true == 1"); + } + + [TestMethod] + public void TestMixedBooleanFloatEquals() + { + TestFails("true == 1.0"); + } + + [TestMethod] + public void TestMixedBooleanStrEquals() + { + TestFails("true == \"true\""); + } +} diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestExponentOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestExponentOperator.cs new file mode 100644 index 0000000..fede6c8 --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestExponentOperator.cs @@ -0,0 +1,152 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using XTMF2.ModelSystemConstruct; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; + +using static XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler.TestCompilerHelper; + +namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; + +[TestClass] +public class TestExponentOperator +{ + [TestMethod] + public void TestExponentInteger() + { + TestExpression("3 ^ 4", 81); + TestExpression("3 ^ 5", 243); + } + + [TestMethod] + public void TestExponentFloat() + { + TestExpression("3.0 ^ 4.0", 81.0f); + TestExpression("3.0 ^ 5.0", 243.0f); + } + + [TestMethod] + public void TestMultipleExponents() + { + TestExpression("1 ^ 2 ^ 3", 1); + } + + [TestMethod] + public void TestMultipleExponentsSpacesOnSides() + { + TestExpression(" 1 ^ 2 ^ 3 ", 1); + } + + [TestMethod] + public void TestExponentExtraOnLeftFails() + { + TestFails("asd 1 ^ 2"); + } + + [TestMethod] + public void TestExponentExtraOnRightFails() + { + TestFails("1 ^ 2 asd"); + } + + [TestMethod] + public void TestExponentAtEndFails() + { + TestFails("1 ^"); + } + + [TestMethod] + public void TestExponentAtStartFails() + { + TestFails("^ 1"); + } + + [TestMethod] + public void TestMixedIntFloatExponent() + { + TestFails("1.0 ^ 1"); + } + + [TestMethod] + public void TestMixedIntStrExponent() + { + TestFails("1 ^ \"Hello\""); + } + + [TestMethod] + public void TestMixedIntBooleanExponent() + { + TestFails("1 ^ true"); + } + + [TestMethod] + public void TestMixedFloatIntExponent() + { + TestFails("1.0 ^ 1"); + } + + [TestMethod] + public void TestMixedFloatStrExponent() + { + TestFails("1.0 ^ \"Hello\""); + } + + [TestMethod] + public void TestMixedFloatBooleanExponent() + { + TestFails("1.0 ^ true"); + } + + [TestMethod] + public void TestMixedStrIntExponent() + { + TestFails("\"1\" ^ 1"); + } + + [TestMethod] + public void TestMixedStrFloatExponent() + { + TestFails("\"1\" ^ 1.0"); + } + + [TestMethod] + public void TestMixedStrBooleanExponent() + { + TestFails("\"1.0\" ^ true"); + } + + [TestMethod] + public void TestMixedBooleanIntExponent() + { + TestFails("true ^ 1"); + } + + [TestMethod] + public void TestMixedBooleanFloatExponent() + { + TestFails("true ^ 1.0"); + } + + [TestMethod] + public void TestMixedBooleanStrExponent() + { + TestFails("true ^ \"true\""); + } +} diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestGreaterThanOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestGreaterThanOperator.cs new file mode 100644 index 0000000..089977c --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestGreaterThanOperator.cs @@ -0,0 +1,144 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using XTMF2.ModelSystemConstruct; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; + +using static XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler.TestCompilerHelper; + +namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; + +[TestClass] +public class TestGreaterThanOperator +{ + [TestMethod] + public void TestGreaterThan() + { + TestExpression("123 > 312", false); + TestExpression("312 > 123", true); + } + + [TestMethod] + public void TestGreaterThanSameValue() + { + TestExpression("123 > 123", false); + } + + [TestMethod] + public void TestGreaterThanFailsTextOnLeft() + { + TestFails("asd 123 > 312"); + } + + [TestMethod] + public void TestGreaterThanFailsTextOnRight() + { + TestFails("123 > 312 asd"); + } + + [TestMethod] + public void TestGreaterThanNothingOnLeftFails() + { + TestFails("> 123"); + } + + [TestMethod] + public void TestGreaterThanNothingOnRightFails() + { + TestFails("123 >"); + } + [TestMethod] + public void TestGreaterThanInString() + { + TestExpression("\"1>2\"", "1>2"); + } + + [TestMethod] + public void TestMixedIntFloatGreatherThan() + { + TestFails("1.0 > 1"); + } + + [TestMethod] + public void TestMixedIntStrGreatherThan() + { + TestFails("1 > \"Hello\""); + } + + [TestMethod] + public void TestMixedIntBooleanGreatherThan() + { + TestFails("1 > true"); + } + + [TestMethod] + public void TestMixedFloatIntGreatherThan() + { + TestFails("1.0 > 1"); + } + + [TestMethod] + public void TestMixedFloatStrGreatherThan() + { + TestFails("1.0 > \"Hello\""); + } + + [TestMethod] + public void TestMixedFloatBooleanGreatherThan() + { + TestFails("1.0 > true"); + } + + [TestMethod] + public void TestMixedStrIntGreatherThan() + { + TestFails("\"1\" > 1"); + } + + [TestMethod] + public void TestMixedStrFloatGreatherThan() + { + TestFails("\"1\" > 1.0"); + } + + [TestMethod] + public void TestMixedStrBooleanGreatherThan() + { + TestFails("\"1.0\" > true"); + } + + [TestMethod] + public void TestMixedBooleanIntGreatherThan() + { + TestFails("true > 1"); + } + + [TestMethod] + public void TestMixedBooleanFloatGreatherThan() + { + TestFails("true > 1.0"); + } + + [TestMethod] + public void TestMixedBooleanStrGreatherThan() + { + TestFails("true > \"true\""); + } +} diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestGreaterThanOrEqualsOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestGreaterThanOrEqualsOperator.cs new file mode 100644 index 0000000..b4060c4 --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestGreaterThanOrEqualsOperator.cs @@ -0,0 +1,145 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using XTMF2.ModelSystemConstruct; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; + +using static XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler.TestCompilerHelper; + +namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; + +[TestClass] +public class TestGreaterThanOrEqualsOperator +{ + [TestMethod] + public void TestGreaterThanOrEquals() + { + TestExpression("123 >= 312", false); + TestExpression("312 >= 123", true); + } + + [TestMethod] + public void TestGreaterThanOrEqualsSameValue() + { + TestExpression("123 >= 123", true); + } + + [TestMethod] + public void TestGreaterThanOrEqualsFailsTextOnLeft() + { + TestFails("asd 123 >= 312"); + } + + [TestMethod] + public void TestGreaterThanOrEqualsFailsTextOnRight() + { + TestFails("123 >= 312 asd"); + } + + [TestMethod] + public void TestGreaterThanEqualsNothingOnLeftFails() + { + TestFails(">= 123"); + } + + [TestMethod] + public void TestGreaterThanEqualsNothingOnRightFails() + { + TestFails("123 >="); + } + + [TestMethod] + public void TestGreaterThanOrEqualsInString() + { + TestExpression("\"1>=2\"", "1>=2"); + } + + [TestMethod] + public void TestMixedIntFloatGreatherThanOrEqual() + { + TestFails("1.0 >= 1"); + } + + [TestMethod] + public void TestMixedIntStrGreatherThanOrEqual() + { + TestFails("1 >= \"Hello\""); + } + + [TestMethod] + public void TestMixedIntBooleanGreatherThanOrEqual() + { + TestFails("1 >= true"); + } + + [TestMethod] + public void TestMixedFloatIntGreatherThanOrEqual() + { + TestFails("1.0 >= 1"); + } + + [TestMethod] + public void TestMixedFloatStrGreatherThanOrEqual() + { + TestFails("1.0 >= \"Hello\""); + } + + [TestMethod] + public void TestMixedFloatBooleanGreatherThanOrEqual() + { + TestFails("1.0 >= true"); + } + + [TestMethod] + public void TestMixedStrIntGreatherThanOrEqual() + { + TestFails("\"1\" >= 1"); + } + + [TestMethod] + public void TestMixedStrFloatGreatherThanOrEqual() + { + TestFails("\"1\" >= 1.0"); + } + + [TestMethod] + public void TestMixedStrBooleanGreatherThanOrEqual() + { + TestFails("\"1.0\" >= true"); + } + + [TestMethod] + public void TestMixedBooleanIntGreatherThanOrEqual() + { + TestFails("true >= 1"); + } + + [TestMethod] + public void TestMixedBooleanFloatGreatherThanOrEqual() + { + TestFails("true >= 1.0"); + } + + [TestMethod] + public void TestMixedBooleanStrGreatherThanOrEqual() + { + TestFails("true >= \"true\""); + } +} diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLessThanOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLessThanOperator.cs new file mode 100644 index 0000000..98459b2 --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLessThanOperator.cs @@ -0,0 +1,144 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using XTMF2.ModelSystemConstruct; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; + +using static XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler.TestCompilerHelper; + +namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; + +[TestClass] +public class TestLessThanOperator +{ + [TestMethod] + public void TestLessThan() + { + TestExpression("123 < 312", true); + } + + [TestMethod] + public void TestLessThanSameValue() + { + TestExpression("123 < 123", false); + } + + [TestMethod] + public void TestLessThanFailsTextOnLeft() + { + TestFails("asd 123 < 312"); + } + + [TestMethod] + public void TestLessThanFailsTextOnRight() + { + TestFails("123 < 312 asd"); + } + + [TestMethod] + public void TestLessThanNothingOnLeftFails() + { + TestFails("< 123"); + } + + [TestMethod] + public void TestLessThanNothingOnRightFails() + { + TestFails("123 <"); + } + + [TestMethod] + public void TestLessThanInString() + { + TestExpression("\"1<2\"", "1<2"); + } + + [TestMethod] + public void TestMixedIntFloatLessThan() + { + TestFails("1.0 < 1"); + } + + [TestMethod] + public void TestMixedIntStrLessThan() + { + TestFails("1 < \"Hello\""); + } + + [TestMethod] + public void TestMixedIntBooleanLessThan() + { + TestFails("1 < true"); + } + + [TestMethod] + public void TestMixedFloatIntLessThan() + { + TestFails("1.0 < 1"); + } + + [TestMethod] + public void TestMixedFloatStrLessThan() + { + TestFails("1.0 < \"Hello\""); + } + + [TestMethod] + public void TestMixedFloatBooleanLessThan() + { + TestFails("1.0 < true"); + } + + [TestMethod] + public void TestMixedStrIntLessThan() + { + TestFails("\"1\" < 1"); + } + + [TestMethod] + public void TestMixedStrFloatLessThan() + { + TestFails("\"1\" < 1.0"); + } + + [TestMethod] + public void TestMixedStrBooleanLessThan() + { + TestFails("\"1.0\" < true"); + } + + [TestMethod] + public void TestMixedBooleanIntLessThan() + { + TestFails("true < 1"); + } + + [TestMethod] + public void TestMixedBooleanFloatLessThan() + { + TestFails("true < 1.0"); + } + + [TestMethod] + public void TestMixedBooleanStrLessThan() + { + TestFails("true < \"true\""); + } +} diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLessThanOrEqualsOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLessThanOrEqualsOperator.cs new file mode 100644 index 0000000..6c72dee --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLessThanOrEqualsOperator.cs @@ -0,0 +1,145 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using XTMF2.ModelSystemConstruct; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; + +using static XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler.TestCompilerHelper; + +namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; + +[TestClass] +public class TestLessThanOrEqualsOperator +{ + [TestMethod] + public void TestLessThanOrEquals() + { + TestExpression("123 <= 312", true); + TestExpression("312 <= 123", false); + } + + [TestMethod] + public void TestLessThanOrEqualsSameValue() + { + TestExpression("123 <= 123", true); + } + + [TestMethod] + public void TestLessThanOrEqualsFailsTextOnLeft() + { + TestFails("asd 123 <= 312"); + } + + [TestMethod] + public void TestLessThanOrEqualsFailsTextOnRight() + { + TestFails("123 <= 312 asd"); + } + + [TestMethod] + public void TestLessThanEqualsNothingOnLeftFails() + { + TestFails("<= 123"); + } + + [TestMethod] + public void TestLessThanEqualsNothingOnRightFails() + { + TestFails("123 <="); + } + + [TestMethod] + public void TestLessThanOrEqualsInString() + { + TestExpression("\"1<=2\"", "1<=2"); + } + + [TestMethod] + public void TestMixedIntFloatLessThanOrEquals() + { + TestFails("1.0 <= 1"); + } + + [TestMethod] + public void TestMixedIntStrLessThanOrEquals() + { + TestFails("1 <= \"Hello\""); + } + + [TestMethod] + public void TestMixedIntBooleanLessThanOrEquals() + { + TestFails("1 <= true"); + } + + [TestMethod] + public void TestMixedFloatIntLessThanOrEquals() + { + TestFails("1.0 <= 1"); + } + + [TestMethod] + public void TestMixedFloatStrLessThanOrEquals() + { + TestFails("1.0 <= \"Hello\""); + } + + [TestMethod] + public void TestMixedFloatBooleanLessThanOrEquals() + { + TestFails("1.0 <= true"); + } + + [TestMethod] + public void TestMixedStrIntLessThanOrEquals() + { + TestFails("\"1\" <= 1"); + } + + [TestMethod] + public void TestMixedStrFloatLessThanOrEquals() + { + TestFails("\"1\" <= 1.0"); + } + + [TestMethod] + public void TestMixedStrBooleanLessThanOrEquals() + { + TestFails("\"1.0\" <= true"); + } + + [TestMethod] + public void TestMixedBooleanIntLessThanOrEquals() + { + TestFails("true <= 1"); + } + + [TestMethod] + public void TestMixedBooleanFloatLessThanOrEquals() + { + TestFails("true <= 1.0"); + } + + [TestMethod] + public void TestMixedBooleanStrLessThanOrEquals() + { + TestFails("true <= \"true\""); + } +} diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLiterals.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLiterals.cs new file mode 100644 index 0000000..683a0ff --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLiterals.cs @@ -0,0 +1,107 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using static XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler.TestCompilerHelper; + +namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; + +[TestClass] +public class TestLiterals +{ + [TestMethod] + public void TestIntegerLiteral() + { + TestExpression("12345", 12345); + } + + [TestMethod] + public void TestBadIntegerLiteral() + { + TestFails("12345abc"); + } + + [TestMethod] + public void TestFloatLiteral() + { + TestExpression("12345.6", 12345.6f); + } + + [TestMethod] + public void TestBadFloatLiteral() + { + TestFails("1234abc.5"); + TestFails("1234.5f"); + TestFails("f1234.5"); + TestFails("1234.5 f"); + } + + [TestMethod] + public void TestBooleanLiteralTrue() + { + TestExpression("True", true); + TestExpression("true", true); + } + + [TestMethod] + public void TestBooleanLiteralFalse() + { + TestExpression("False", false); + TestExpression("false", false); + } + + [TestMethod] + public void TestStringLiteral() + { + TestExpression("\"12345.6\"", "12345.6"); + } + + [TestMethod] + public void TestStringLiteralMissingLeft() + { + TestFails("12345.6\""); + } + + [TestMethod] + public void TestStringLiteralMissingRight() + { + TestFails("\"12345.6"); + } + + [TestMethod] + public void TestWhitespaceBeforeStringLiteral() + { + TestExpression(" \"12345.6\"", "12345.6"); + } + + [TestMethod] + public void TestWhitespaceAfterStringLiteral() + { + TestExpression("\"12345.6\" ", "12345.6"); + } + + [TestMethod] + public void TestBadStringLiteral() + { + TestFails("\"No final quote"); + TestFails("Text before quote \""); + TestFails("\"Text\" Text after quote"); + TestFails("Text before quote \"Text\""); + } +} diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestMultiplyOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestMultiplyOperator.cs new file mode 100644 index 0000000..a979bbb --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestMultiplyOperator.cs @@ -0,0 +1,156 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using XTMF2.ModelSystemConstruct; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; + +using static XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler.TestCompilerHelper; + +namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; + +[TestClass] +public class TestMultiplyOperator +{ + [TestMethod] + public void TestMultiply() + { + TestExpression("123 * 312", 38376); + } + + [TestMethod] + public void TestMultiplyFloat() + { + TestExpression("123.0 * 312.0", 38376.0f); + } + + [TestMethod] + public void TestMultipleMultiplications() + { + TestExpression("1 * 2 * 3", 6); + } + + [TestMethod] + public void TestMultipleMultiplicationsSpacesOnSides() + { + TestExpression(" 1 * 2 * 3 ", 6); + } + + [TestMethod] + public void TestMultiplyExtraOnLeftFails() + { + TestFails("asd 1 * 2"); + } + + [TestMethod] + public void TestMultiplyExtraOnRightFails() + { + TestFails("1 * 2 asd"); + } + + [TestMethod] + public void TestMultiplyAtEndFails() + { + TestFails("1 *"); + } + + [TestMethod] + public void TestMultiplyAtStartFails() + { + TestFails("* 1"); + } + + [TestMethod] + public void TestMultiplyInString() + { + TestExpression("\"1*2\"", "1*2"); + } + + [TestMethod] + public void TestMixedIntFloatMultiply() + { + TestFails("1.0 * 1"); + } + + [TestMethod] + public void TestMixedIntStrMultiply() + { + TestFails("1 * \"Hello\""); + } + + [TestMethod] + public void TestMixedIntBooleanMultiply() + { + TestFails("1 * true"); + } + + [TestMethod] + public void TestMixedFloatIntMultiply() + { + TestFails("1.0 * 1"); + } + + [TestMethod] + public void TestMixedFloatStrMultiply() + { + TestFails("1.0 * \"Hello\""); + } + + [TestMethod] + public void TestMixedFloatBooleanMultiply() + { + TestFails("1.0 * true"); + } + + [TestMethod] + public void TestMixedStrIntMultiply() + { + TestFails("\"1\" * 1"); + } + + [TestMethod] + public void TestMixedStrFloatMultiply() + { + TestFails("\"1\" * 1.0"); + } + + [TestMethod] + public void TestMixedStrBooleanMultiply() + { + TestFails("\"1.0\" * true"); + } + + [TestMethod] + public void TestMixedBooleanIntMultiply() + { + TestFails("true * 1"); + } + + [TestMethod] + public void TestMixedBooleanFloatMultiply() + { + TestFails("true * 1.0"); + } + + [TestMethod] + public void TestMixedBooleanStrMultiply() + { + TestFails("true * \"true\""); + } +} diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestNotEqualsOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestNotEqualsOperator.cs new file mode 100644 index 0000000..4c573dd --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestNotEqualsOperator.cs @@ -0,0 +1,172 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using XTMF2.ModelSystemConstruct; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; + +using static XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler.TestCompilerHelper; + +namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; + +[TestClass] +public class TestNotEqualsOperator +{ + [TestMethod] + public void TestDifferentValue() + { + TestExpression("123 != 312", true); + } + + [TestMethod] + public void TestSameValue() + { + TestExpression("123 != 123", false); + } + + [TestMethod] + public void TestFailsTextOnLeft() + { + TestFails("asd 123 != 312"); + } + + [TestMethod] + public void TesFailsTextOnRight() + { + TestFails("123 != 312 asd"); + } + + [TestMethod] + public void TestNothingOnLeftFails() + { + TestFails("!= 123"); + } + + [TestMethod] + public void TestNothingOnRightFails() + { + TestFails("123 !="); + } + + [TestMethod] + public void TestNotEqualsInString() + { + TestExpression("\"true!=false\"", "true!=false"); + } + + [TestMethod] + public void TestNotEqualsAfterLessThan() + { + TestExpression("123 < 312 != true", false); + TestExpression("312 < 123 != true", true); + } + + [TestMethod] + public void TestNotEqualsAfterLessThanOrEqualsThan() + { + TestExpression("123 <= 312 != true", false); + TestExpression("312 <= 123 != true", true); + } + + [TestMethod] + public void TestNotEqualsAfterGreaterThan() + { + TestExpression("123 > 312 != true", true); + TestExpression("312 > 123 != true", false); + } + + [TestMethod] + public void TestNotEqualsAfterGreaterThanOrEqualsThan() + { + TestExpression("123 >= 312 != true", true); + TestExpression("312 >= 123 != true", false); + } + + [TestMethod] + public void TestMixedIntFloatNotEquals() + { + TestFails("1.0 != 1"); + } + + [TestMethod] + public void TestMixedIntStrNotEquals() + { + TestFails("1 != \"Hello\""); + } + + [TestMethod] + public void TestMixedIntBooleanNotEquals() + { + TestFails("1 != true"); + } + + [TestMethod] + public void TestMixedFloatIntNotEquals() + { + TestFails("1.0 != 1"); + } + + [TestMethod] + public void TestMixedFloatStrNotEquals() + { + TestFails("1.0 != \"Hello\""); + } + + [TestMethod] + public void TestMixedFloatBooleanNotEquals() + { + TestFails("1.0 != true"); + } + + [TestMethod] + public void TestMixedStrIntNotEquals() + { + TestFails("\"1\" != 1"); + } + + [TestMethod] + public void TestMixedStrFloatNotEquals() + { + TestFails("\"1\" != 1.0"); + } + + [TestMethod] + public void TestMixedStrBooleanNotEquals() + { + TestFails("\"1.0\" != true"); + } + + [TestMethod] + public void TestMixedBooleanIntNotEquals() + { + TestFails("true != 1"); + } + + [TestMethod] + public void TestMixedBooleanFloatNotEquals() + { + TestFails("true != 1.0"); + } + + [TestMethod] + public void TestMixedBooleanStrNotEquals() + { + TestFails("true != \"true\""); + } +} diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestNotOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestNotOperator.cs new file mode 100644 index 0000000..ca54f6f --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestNotOperator.cs @@ -0,0 +1,130 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using XTMF2.Editing; +using XTMF2.ModelSystemConstruct; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; + +using static XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler.TestCompilerHelper; + +namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; + +[TestClass] +public class TestNotOperator +{ + + [TestMethod] + public void TestNotTrue() + { + TestExpression("!True", false); + } + + + [TestMethod] + public void TestNotTrueInBrackets() + { + TestExpression("!(True)", false); + } + + [TestMethod] + public void TestTextAfterFailsInBrackets() + { + TestFails("!(True asd)"); + } + + [TestMethod] + public void TestNotFalse() + { + TestExpression("!False", true); + } + + [TestMethod] + public void TestNotTrueWithSpace() + { + TestExpression("! True", false); + } + + [TestMethod] + public void TestNotFalseWithSpace() + { + TestExpression("! False", true); + } + + [TestMethod] + public void TestNotTrueWithSpaceBefore() + { + TestExpression(" !True", false); + } + + [TestMethod] + public void TestNotTrueWithSpaceAfter() + { + TestExpression("!True ", false); + } + + [TestMethod] + public void TestTextBeforeFails() + { + TestFails("asd !True"); + } + + [TestMethod] + public void TestTextAfterFails() + { + TestFails("!True asd"); + } + + [TestMethod] + public void TestNonBoolTextFails() + { + TestFails("!asd"); + } + + [TestMethod] + public void TestNotVariable() + { + TestHelper.RunInModelSystemContext("TestBadVariableNames", (User user, ProjectSession project, ModelSystemSession session) => + { + var nodes = new List() + { + CreateNodeForVariable(session, user, "booleanVar", "true") + }; + TestExpression("!booleanVar", false, nodes); + }); + } + + [TestMethod] + public void TestNotIntegerLiteralFails() + { + TestFails("!12345"); + } + + [TestMethod] + public void TestNotFloatLiteralFails() + { + TestFails("!12345.6"); + } + + [TestMethod] + public void TestNotStringLiteralFails() + { + TestFails("!\"true\""); + } +} diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestOrOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestOrOperator.cs new file mode 100644 index 0000000..5b9abb3 --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestOrOperator.cs @@ -0,0 +1,155 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using XTMF2.ModelSystemConstruct; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; + +using static XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler.TestCompilerHelper; +namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; + +[TestClass] +public class TestOrOperator +{ + [TestMethod] + public void TestOr() + { + TestExpression("true || true", true); + TestExpression("true || false", true); + TestExpression("false || true", true); + TestExpression("false || false", false); + } + + [TestMethod] + public void TestOrNoSpaces() + { + TestExpression("true||true", true); + TestExpression("true||false", true); + TestExpression("false||true", true); + TestExpression("false||false", false); + } + + [TestMethod] + public void TestOrOutsideOfAnd() + { + TestExpression("true || true && false", true); + } + + [TestMethod] + public void TestOrInString() + { + TestExpression("\"true || true\"", "true || true"); + } + + [TestMethod] + public void TestOrWithIntegers() + { + TestFails("1||2"); + } + + [TestMethod] + public void TestOrWithFloats() + { + TestFails("1.0||2.0"); + } + + [TestMethod] + public void TestOrWithStrings() + { + TestFails("\"1.0\"||\"2.0\""); + } + + [TestMethod] + public void TestOrOutsideOfEquals() + { + TestExpression("false || true == false", false); + } + + [TestMethod] + public void TestMixedIntFloatOr() + { + TestFails("1.0 || 1"); + } + + [TestMethod] + public void TestMixedIntStrOr() + { + TestFails("1 || \"Hello\""); + } + + [TestMethod] + public void TestMixedIntBooleanOr() + { + TestFails("1 || true"); + } + + [TestMethod] + public void TestMixedFloatIntOr() + { + TestFails("1.0 || 1"); + } + + [TestMethod] + public void TestMixedFloatStrOr() + { + TestFails("1.0 || \"Hello\""); + } + + [TestMethod] + public void TestMixedFloatBooleanOr() + { + TestFails("1.0 || true"); + } + + [TestMethod] + public void TestMixedStrIntOr() + { + TestFails("\"1\" || 1"); + } + + [TestMethod] + public void TestMixedStrFloatOr() + { + TestFails("\"1\" || 1.0"); + } + + [TestMethod] + public void TestMixedStrBooleanOr() + { + TestFails("\"1.0\" || true"); + } + + [TestMethod] + public void TestMixedBooleanIntOr() + { + TestFails("true || 1"); + } + + [TestMethod] + public void TestMixedBooleanFloatOr() + { + TestFails("true || 1.0"); + } + + [TestMethod] + public void TestMixedBooleanStrOr() + { + TestFails("true || \"true\""); + } +} diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestSelectOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestSelectOperator.cs new file mode 100644 index 0000000..2fee2b9 --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestSelectOperator.cs @@ -0,0 +1,199 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using XTMF2.ModelSystemConstruct; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; + +using static XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler.TestCompilerHelper; + +namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; + +[TestClass] +public class TestSelectOperator +{ + [TestMethod] + public void TestSelectInteger() + { + TestExpression("true?1:2", 1); + TestExpression("false?1:2", 2); + } + + [TestMethod] + public void TestMultipleSelects() + { + TestExpression("true?true?1:2:3", 1); + TestExpression("true?false?1:2:3", 2); + TestExpression("false?false?1:2:3", 3); + } + + [TestMethod] + public void TestSelectFloat() + { + TestExpression("true?1.0:2.0", 1.0f); + TestExpression("false?1.0:2.0", 2.0f); + } + + [TestMethod] + public void TestSelectString() + { + TestExpression("true?\"1.0\":\"2.0\"", "1.0"); + TestExpression("false?\"1.0\":\"2.0\"", "2.0"); + } + + [TestMethod] + public void TestSelectStringFailsTextOnLeft() + { + TestFails("asd true?\"1.0\":\"2.0\""); + } + + [TestMethod] + public void TestSelectStringFailsTextOnRight() + { + TestFails("true?\"1.0\":\"2.0\" asd"); + } + + [TestMethod] + public void TestSelectStringFailsMissingCondition() + { + TestFails("?\"1.0\":\"2.0\""); + } + + [TestMethod] + public void TestSelectStringFailsMissingTrue() + { + TestFails("true?:\"2.0\""); + } + + [TestMethod] + public void TestSelectStringFailsMissingFalse() + { + TestFails("true?\"1.0\":"); + } + + [TestMethod] + public void TestSelectStringFailsMissingCaseSeperator() + { + TestFails("true?\"1.0\" \"2.0\""); + } + + [TestMethod] + public void TestIntegerConditionFails() + { + TestFails("1?\"1.0\" \"2.0\""); + } + + [TestMethod] + public void TestFloatConditionFails() + { + TestFails("1.0?\"1.0\" \"2.0\""); + } + + [TestMethod] + public void TestStringConditionFails() + { + TestFails("\"1.0\"?\"1.0\" \"2.0\""); + } + + [TestMethod] + public void TestNoSelectOperatorWithBreak() + { + TestFails("true 1 : 2"); + TestFails("1:2"); + TestFails(":"); + } + + [TestMethod] + public void TestConditionOperatorInString() + { + TestExpression("\"true?1:0\"", "true?1:0"); + } + + [TestMethod] + public void TestMixedIntFloatSelect() + { + TestFails("true ? 1.0 : 1"); + } + + [TestMethod] + public void TestMixedIntStrSelect() + { + TestFails("true ? 1 : \"Hello\""); + } + + [TestMethod] + public void TestMixedIntBooleanSelect() + { + TestFails("true ? 1 : true"); + } + + [TestMethod] + public void TestMixedFloatIntSelect() + { + TestFails("true ? 1.0 : 1"); + } + + [TestMethod] + public void TestMixedFloatStrSelect() + { + TestFails("true ? 1.0 : \"Hello\""); + } + + [TestMethod] + public void TestMixedFloatBooleanSelect() + { + TestFails("true ? 1.0 : true"); + } + + [TestMethod] + public void TestMixedStrIntSelect() + { + TestFails("true ? \"1\" : 1"); + } + + [TestMethod] + public void TestMixedStrFloatSelect() + { + TestFails("true ? \"1\" : 1.0"); + } + + [TestMethod] + public void TestMixedStrBooleanSelect() + { + TestFails("true ? \"1.0\" : true"); + } + + [TestMethod] + public void TestMixedBooleanIntSelect() + { + TestFails("true ? true : 1"); + } + + [TestMethod] + public void TestMixedBooleanFloatSelect() + { + TestFails("true ? true : 1.0"); + } + + [TestMethod] + public void TestMixedBooleanStrSelect() + { + TestFails("true ? true : \"true\""); + } +} diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestSubtractOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestSubtractOperator.cs new file mode 100644 index 0000000..86c09a8 --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestSubtractOperator.cs @@ -0,0 +1,144 @@ +/* + Copyright 2022 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using XTMF2.ModelSystemConstruct; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; + +using static XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler.TestCompilerHelper; + +namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; + +[TestClass] +public class TestSubtractOperator +{ + [TestMethod] + public void TestSubtract() + { + TestExpression("123 - 312", -189); + } + + [TestMethod] + public void TestSubtractFloat() + { + TestExpression("123.0 - 312.0", -189.0f); + } + + [TestMethod] + public void TestMultipleSubtracts() + { + TestExpression("1 - 2 - 3", -4); + } + + [TestMethod] + public void TestMultipleSubtractsSpacesOnSides() + { + TestExpression(" 1 - 2 - 3 ", -4); + } + + [TestMethod] + public void TestSubtractExtraOnLeftFails() + { + TestFails("asd 1 - 2"); + } + + [TestMethod] + public void TestSubtractExtraOnRightFails() + { + TestFails("1 - 2 asd"); + } + + [TestMethod] + public void TestSubtractAtEndFails() + { + TestFails("1 -"); + } + + [TestMethod] + public void TestMixedIntFloatSubtract() + { + TestFails("1.0 - 1"); + } + + [TestMethod] + public void TestMixedIntStrSubtract() + { + TestFails("1 - \"Hello\""); + } + + [TestMethod] + public void TestMixedIntBooleanSubtract() + { + TestFails("1 - true"); + } + + [TestMethod] + public void TestMixedFloatIntSubtract() + { + TestFails("1.0 - 1"); + } + + [TestMethod] + public void TestMixedFloatStrSubtract() + { + TestFails("1.0 - \"Hello\""); + } + + [TestMethod] + public void TestMixedFloatBooleanSubtract() + { + TestFails("1.0 - true"); + } + + [TestMethod] + public void TestMixedStrIntSubtract() + { + TestFails("\"1\" - 1"); + } + + [TestMethod] + public void TestMixedStrFloatSubtract() + { + TestFails("\"1\" - 1.0"); + } + + [TestMethod] + public void TestMixedStrBooleanSubtract() + { + TestFails("\"1.0\" - true"); + } + + [TestMethod] + public void TestMixedBooleanIntSubtract() + { + TestFails("true - 1"); + } + + [TestMethod] + public void TestMixedBooleanFloatSubtract() + { + TestFails("true - 1.0"); + } + + [TestMethod] + public void TestMixedBooleanStrSubtract() + { + TestFails("true - \"true\""); + } +} diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestVariables.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestVariables.cs new file mode 100644 index 0000000..59c850b --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestVariables.cs @@ -0,0 +1,174 @@ +/* + Copyright 2017-2021 University of Toronto + This file is part of XTMF2. + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using XTMF2.Editing; +using XTMF2.ModelSystemConstruct; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; +using XTMF2.RuntimeModules; + +using static XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler.TestCompilerHelper; + +namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; + +[TestClass] +public class TestVariables +{ + [TestMethod] + public void TestBooleanVariable() + { + TestHelper.RunInModelSystemContext("TestBooleanVariable", (User user, ProjectSession project, ModelSystemSession session) => + { + string error = null; + var nodes = new List() + { + CreateNodeForVariable(session, user, "myBoolVariable", "true") + }; + var text = "myBoolVariable"; + Assert.IsTrue(ParameterCompiler.CreateExpression(nodes, text, out var expression, ref error), $"Failed to compile {text}"); + Assert.IsNotNull(expression); + Assert.AreEqual(typeof(bool), expression.Type); + Assert.IsTrue(ParameterCompiler.Evaluate(null, expression, out var result, ref error), error); + if (result is bool boolResult) + { + Assert.IsTrue(boolResult); + } + else + { + Assert.Fail("The result is not a boolean!"); + } + }); + } + + [TestMethod] + public void TestIntegerVariable() + { + TestHelper.RunInModelSystemContext("TestIntegerVariable", (User user, ProjectSession project, ModelSystemSession session) => + { + string error = null; + var nodes = new List() + { + CreateNodeForVariable(session, user, "myIntVariable", "12345") + }; + var text = "myIntVariable"; + Assert.IsTrue(ParameterCompiler.CreateExpression(nodes, text, out var expression, ref error), $"Failed to compile {text}"); + Assert.IsNotNull(expression); + Assert.AreEqual(typeof(int), expression.Type); + Assert.IsTrue(ParameterCompiler.Evaluate(null, expression, out var result, ref error), error); + if (result is int intResult) + { + Assert.AreEqual(12345, intResult); + } + else + { + Assert.Fail("The result is not an integer!"); + } + }); + } + + [TestMethod] + public void TestFloatVariable() + { + TestHelper.RunInModelSystemContext("TestFloatVariable", (User user, ProjectSession project, ModelSystemSession session) => + { + string error = null; + var nodes = new List() + { + CreateNodeForVariable(session, user, "myFloatVariable", "12345.6") + }; + var text = "myFloatVariable"; + Assert.IsTrue(ParameterCompiler.CreateExpression(nodes, text, out var expression, ref error), $"Failed to compile {text}"); + Assert.IsNotNull(expression); + Assert.AreEqual(typeof(float), expression.Type); + Assert.IsTrue(ParameterCompiler.Evaluate(null, expression, out var result, ref error), error); + if (result is float floatResult) + { + Assert.AreEqual(12345.6, floatResult, 0.001f); + } + else + { + Assert.Fail("The result is not an float!"); + } + }); + } + + [TestMethod] + public void TestStringVariable() + { + TestHelper.RunInModelSystemContext("TestStringVariable", (User user, ProjectSession project, ModelSystemSession session) => + { + string error = null; + var nodes = new List() + { + CreateNodeForVariable(session, user, "myStringVariable", "12345.6") + }; + var text = "myStringVariable"; + Assert.IsTrue(ParameterCompiler.CreateExpression(nodes, text, out var expression, ref error), $"Failed to compile {text}"); + Assert.IsNotNull(expression); + Assert.AreEqual(typeof(string), expression.Type); + Assert.IsTrue(ParameterCompiler.Evaluate(null, expression, out var result, ref error), error); + if (result is string strResult) + { + Assert.AreEqual("12345.6", strResult); + } + else + { + Assert.Fail("The result is not a string!"); + } + }); + } + + [TestMethod] + public void TestWhitespaceAroundVariable() + { + TestHelper.RunInModelSystemContext("TestWhitespaceAroundVariable", (User user, ProjectSession project, ModelSystemSession session) => + { + string error = null; + var nodes = new List() + { + CreateNodeForVariable(session, user, "myStringVariable", "12345.6") + }; + var text = " myStringVariable "; + Assert.IsTrue(ParameterCompiler.CreateExpression(nodes, text, out var expression, ref error), $"Failed to compile {text}"); + Assert.IsNotNull(expression); + Assert.AreEqual(typeof(string), expression.Type); + Assert.IsTrue(ParameterCompiler.Evaluate(null, expression, out var result, ref error), error); + if (result is string strResult) + { + Assert.AreEqual("12345.6", strResult); + } + else + { + Assert.Fail("The result is not a string!"); + } + }); + } + + [TestMethod] + public void TestBadVariableNames() + { + TestHelper.RunInModelSystemContext("TestBadVariableNames", (User user, ProjectSession project, ModelSystemSession session) => + { + var nodes = new List() + { + CreateNodeForVariable(session, user, "myStringVariable", "12345.6") + }; + TestFails("myStringVariable asd", nodes); + TestFails("asd myStringVariable", nodes); + TestFails("wrongVariableName", nodes); + }); + } +} diff --git a/tests/XTMF2.UnitTests/TestModelSystem.cs b/tests/XTMF2.UnitTests/TestModelSystem.cs index 036f164..293aee7 100644 --- a/tests/XTMF2.UnitTests/TestModelSystem.cs +++ b/tests/XTMF2.UnitTests/TestModelSystem.cs @@ -60,13 +60,37 @@ public void ModelSystemPersistance() Assert.IsTrue(projectController.GetProjectSession(user, user.AvailableProjects[0], out session, out error).UsingIf(session, () => { var modelSystems = session.ModelSystems; - Assert.AreEqual(1, modelSystems.Count); + Assert.HasCount(1, modelSystems); Assert.AreEqual(modelSystemName, modelSystems[0].Name); }), error?.Message); //cleanup userController.Delete(user); } + [TestMethod] + public void CreateModelSystemOrGet() + { + var runtime = XTMFRuntime.CreateRuntime(); + var userController = runtime.UserController; + var projectController = runtime.ProjectController; + CommandError error = null; + const string userName = "NewUser"; + const string projectName = "TestProject"; + const string modelSystemName = "ModelSystem1"; + // clear out the user if possible + userController.Delete(userName); + Assert.IsTrue(userController.CreateNew(userName, false, out var user, out error), error?.Message); + Assert.IsTrue(projectController.CreateNewProject(user, projectName, out var session, out error).UsingIf(session, () => + { + Assert.IsTrue(session.CreateOrGetModelSystem(user, modelSystemName, out var modelSystemHeader, out error), error?.Message); + Assert.IsTrue(session.CreateOrGetModelSystem(user, modelSystemName, out var modelSystemHeader2, out error), error?.Message); + Assert.AreSame(modelSystemHeader, modelSystemHeader2); + Assert.IsTrue(session.Save(out error)); + }), error?.Message); + //cleanup + userController.Delete(user); + } + [TestMethod] public void GetModelSystemSession() { @@ -169,7 +193,7 @@ public void ModelSystemSavedWithStartOnly() // after shutdown var ms = mSession.ModelSystem; CommandError error = null; - Assert.AreEqual(1, ms.GlobalBoundary.Starts.Count); + Assert.HasCount(1, ms.GlobalBoundary.Starts); // we shouldn't be able to add another start with the same name in the same boundary Assert.IsFalse(mSession.AddModelSystemStart(user, ms.GlobalBoundary, "FirstStart", Rectangle.Hidden, out var start, out error), error?.Message); }); @@ -189,7 +213,7 @@ public void ModelSystemSavedWithNodeOnly() { // after shutdown var ms = mSession.ModelSystem; - Assert.AreEqual(1, ms.GlobalBoundary.Modules.Count); + Assert.HasCount(1, ms.GlobalBoundary.Modules); }); } @@ -209,8 +233,8 @@ public void ModelSystemSavedWithStartAndNode() // after shutdown CommandError error = null; var ms = mSession.ModelSystem; - Assert.AreEqual(1, ms.GlobalBoundary.Starts.Count); - Assert.AreEqual(1, ms.GlobalBoundary.Modules.Count); + Assert.HasCount(1, ms.GlobalBoundary.Starts); + Assert.HasCount(1, ms.GlobalBoundary.Modules); Assert.IsFalse(mSession.AddModelSystemStart(user, ms.GlobalBoundary, "FirstStart", Rectangle.Hidden, out var start, out error), error?.Message); }); } @@ -232,9 +256,9 @@ public void ModelSystemWithLink() // after shutdown CommandError error = null; var ms = mSession.ModelSystem; - Assert.AreEqual(1, ms.GlobalBoundary.Starts.Count); - Assert.AreEqual(1, ms.GlobalBoundary.Modules.Count); - Assert.AreEqual(1, ms.GlobalBoundary.Links.Count); + Assert.HasCount(1, ms.GlobalBoundary.Starts); + Assert.HasCount(1, ms.GlobalBoundary.Modules); + Assert.HasCount(1, ms.GlobalBoundary.Links); Assert.IsFalse(mSession.AddModelSystemStart(user, ms.GlobalBoundary, "FirstStart", Rectangle.Hidden, out var start, out error), error?.Message); }); @@ -280,9 +304,9 @@ public void ModelSystemWithMultiLink() // after shutdown CommandError error = null; var ms = mSession.ModelSystem; - Assert.AreEqual(1, ms.GlobalBoundary.Starts.Count); - Assert.AreEqual(5, ms.GlobalBoundary.Modules.Count); - Assert.AreEqual(5, ms.GlobalBoundary.Links.Count); + Assert.HasCount(1, ms.GlobalBoundary.Starts); + Assert.HasCount(5, ms.GlobalBoundary.Modules); + Assert.HasCount(5, ms.GlobalBoundary.Links); Assert.IsFalse(mSession.AddModelSystemStart(user, ms.GlobalBoundary, "FirstStart", Rectangle.Hidden, out var start, out error), error?.Message); }); @@ -377,11 +401,11 @@ public void ExportModelSystemMetaData() using (var entryStream = entry.Open()) { buffer = new byte[entry.Length]; - entryStream.Read(buffer, 0, buffer.Length); + entryStream.ReadExactly(buffer, 0, buffer.Length); } var reader = new Utf8JsonReader(buffer); Assert.IsTrue(reader.Read(), "Unable to read the initial object."); - Assert.IsTrue(reader.TokenType == JsonTokenType.StartObject, "The first element was not a start object"); + Assert.AreEqual(JsonTokenType.StartObject, reader.TokenType, "The first element was not a start object"); bool readName = false, readDescription = false, readExportedOn = false, readExportedBy = false, readVersionMajor = false, readVersionMinor = false; while (reader.Read()) diff --git a/tests/XTMF2.UnitTests/TestProjects.cs b/tests/XTMF2.UnitTests/TestProjects.cs index 9146fff..c7aa38b 100644 --- a/tests/XTMF2.UnitTests/TestProjects.cs +++ b/tests/XTMF2.UnitTests/TestProjects.cs @@ -47,6 +47,30 @@ public void CreateNewProject() Assert.IsFalse(controller.DeleteProject(localUser, "Test", out error)); } + [TestMethod] + public void CreateNewOrGet() + { + var runtime = XTMFRuntime.CreateRuntime(); + var controller = runtime.ProjectController; + CommandError error = null; + var localUser = TestHelper.GetTestUser(runtime); + // delete the project in case it has survived. + controller.DeleteProject(localUser, "Test", out error); + Assert.IsTrue(controller.CreateNewOrGet(localUser, "Test", out var session, out error).UsingIf(session, () => + { + var project = session.Project; + Assert.AreEqual("Test", project.Name); + Assert.AreEqual(localUser, project.Owner); + }), "Unable to create project"); + + Assert.IsTrue(controller.CreateNewOrGet(localUser, "Test", out session, out error).UsingIf(session, () => + { + + }), "Unable to get the project the second time."); + Assert.IsTrue(controller.DeleteProject(localUser, "Test", out error)); + Assert.IsFalse(controller.DeleteProject(localUser, "Test", out error)); + } + [TestMethod] public void RenameProject() { @@ -93,7 +117,7 @@ public void ProjectPersistance() runtime = XTMFRuntime.CreateRuntime(); controller = runtime.ProjectController; localUser = TestHelper.GetTestUser(runtime); - Assert.AreEqual(numberOfProjects, localUser.AvailableProjects.Count); + Assert.HasCount(numberOfProjects, localUser.AvailableProjects); var regainedProject = localUser.AvailableProjects[0]; Assert.AreEqual(projectName, regainedProject.Name); } @@ -296,7 +320,7 @@ public void ImportProjectFileNoModelSystem() using (importedSession) { var modelSystems = importedSession.ModelSystems; - Assert.AreEqual(0, modelSystems.Count); + Assert.IsEmpty(modelSystems); } } finally @@ -334,7 +358,7 @@ public void ImportProjectFileSingleModelSystem() using (importedSession) { var modelSystems = importedSession.ModelSystems; - Assert.AreEqual(1, modelSystems.Count); + Assert.HasCount(1, modelSystems); } } finally @@ -376,7 +400,7 @@ public void ImportProjectFileMultipleModelSystem() using (importedSession) { var modelSystems = importedSession.ModelSystems; - Assert.AreEqual(numberOfModelSystems, modelSystems.Count); + Assert.HasCount(numberOfModelSystems, modelSystems); } } finally @@ -416,7 +440,7 @@ public void ImportProjectFileAddedToController() using (importedSession) { var modelSystems = importedSession.ModelSystems; - Assert.AreEqual(0, modelSystems.Count); + Assert.IsEmpty(modelSystems); } Assert.IsTrue(user.AvailableProjects.Any(p => p.Name == ImportedModelSystemName), "The imported project was not available to use user."); } @@ -518,9 +542,9 @@ public void AddAdditionalPastRunDirectory() string path = Path.GetTempPath(); CommandError error = null; var previousRunDirectories = project.AdditionalPreviousRunDirectories; - Assert.AreEqual(0, previousRunDirectories.Count, "There were already previous run directories!"); + Assert.IsEmpty(previousRunDirectories, "There were already previous run directories!"); Assert.IsTrue(project.AddAdditionalPastRunDirectory(user, path, out error), error?.Message ?? "Failed to have an error message!"); - Assert.AreEqual(1, previousRunDirectories.Count, "The previous runs did not include the new path!"); + Assert.HasCount(1, previousRunDirectories, "The previous runs did not include the new path!"); Assert.AreEqual(path, previousRunDirectories[0], "The path is not the same!"); }); } @@ -531,10 +555,10 @@ public void AddAdditionalPastRunDirectory_Null() TestHelper.RunInProjectContext("AddAdditionalPastRunDirectory_Null", (User user, ProjectSession project) => { var previousRunDirectories = project.AdditionalPreviousRunDirectories; - Assert.AreEqual(0, previousRunDirectories.Count, "There were already previous run directories!"); + Assert.IsEmpty(previousRunDirectories, "There were already previous run directories!"); Assert.IsFalse(project.AddAdditionalPastRunDirectory(user, null, out CommandError error), "The add operation succeeded even though it should have failed!"); - Assert.AreEqual(0, previousRunDirectories.Count, "The invalid previous run directory was added!"); + Assert.IsEmpty(previousRunDirectories, "The invalid previous run directory was added!"); }); } @@ -544,10 +568,10 @@ public void AddAdditionalPastRunDirectory_EmptyString() TestHelper.RunInProjectContext("AddAdditionalPastRunDirectory_EmptyString", (User user, ProjectSession project) => { var previousRunDirectories = project.AdditionalPreviousRunDirectories; - Assert.AreEqual(0, previousRunDirectories.Count, "There were already previous run directories!"); + Assert.IsEmpty(previousRunDirectories, "There were already previous run directories!"); Assert.IsFalse(project.AddAdditionalPastRunDirectory(user, String.Empty, out CommandError error), "The add operation succeeded even though it should have failed!"); - Assert.AreEqual(0, previousRunDirectories.Count, "The invalid previous run directory was added!"); + Assert.IsEmpty(previousRunDirectories, "The invalid previous run directory was added!"); }); } @@ -559,12 +583,12 @@ public void RemoveAdditionalPastRunDirectory() string path = Path.GetTempPath(); CommandError error = null; var previousRunDirectories = project.AdditionalPreviousRunDirectories; - Assert.AreEqual(0, previousRunDirectories.Count, "There were already previous run directories!"); + Assert.IsEmpty(previousRunDirectories, "There were already previous run directories!"); Assert.IsTrue(project.AddAdditionalPastRunDirectory(user, path, out error), error?.Message ?? "Failed to have an error message!"); - Assert.AreEqual(1, previousRunDirectories.Count, "The previous runs did not include the new path!"); + Assert.HasCount(1, previousRunDirectories, "The previous runs did not include the new path!"); Assert.AreEqual(path, previousRunDirectories[0], "The path is not the same!"); Assert.IsTrue(project.RemoveAdditionalPastRunDirectory(user, path, out error), error?.Message ?? "Failed to have an error message!"); - Assert.AreEqual(0, previousRunDirectories.Count, "The previous run directory was not removed!"); + Assert.IsEmpty(previousRunDirectories, "The previous run directory was not removed!"); }); } @@ -576,11 +600,11 @@ public void RemoveAdditionalPastRunDirectory_Null() string path = Path.GetTempPath(); CommandError error = null; var previousRunDirectories = project.AdditionalPreviousRunDirectories; - Assert.AreEqual(0, previousRunDirectories.Count, "There were already previous run directories!"); + Assert.IsEmpty(previousRunDirectories, "There were already previous run directories!"); Assert.IsTrue(project.AddAdditionalPastRunDirectory(user, path, out error), error?.Message ?? "Failed to have an error message!"); Assert.IsFalse(project.RemoveAdditionalPastRunDirectory(user, null, out error), "The remove operation succeeded even though it should have failed!"); - Assert.AreEqual(1, previousRunDirectories.Count, "The previous run directory was removed!"); + Assert.HasCount(1, previousRunDirectories, "The previous run directory was removed!"); }); } @@ -592,11 +616,11 @@ public void RemoveAdditionalPastRunDirectory_EmptyString() string path = Path.GetTempPath(); CommandError error = null; var previousRunDirectories = project.AdditionalPreviousRunDirectories; - Assert.AreEqual(0, previousRunDirectories.Count, "There were already previous run directories!"); + Assert.IsEmpty(previousRunDirectories, "There were already previous run directories!"); Assert.IsTrue(project.AddAdditionalPastRunDirectory(user, path, out error), error?.Message ?? "Failed to have an error message!"); Assert.IsFalse(project.RemoveAdditionalPastRunDirectory(user, String.Empty, out error), "The remove operation succeeded even though it should have failed!"); - Assert.AreEqual(1, previousRunDirectories.Count, "The previous run directory was removed!"); + Assert.HasCount(1, previousRunDirectories, "The previous run directory was removed!"); }); } @@ -608,11 +632,11 @@ public void RemoveAdditionalPastRunDirectory_NonExistent() string path = Path.GetTempPath(); CommandError error = null; var previousRunDirectories = project.AdditionalPreviousRunDirectories; - Assert.AreEqual(0, previousRunDirectories.Count, "There were already previous run directories!"); + Assert.IsEmpty(previousRunDirectories, "There were already previous run directories!"); Assert.IsTrue(project.AddAdditionalPastRunDirectory(user, path, out error), error?.Message ?? "Failed to have an error message!"); Assert.IsFalse(project.RemoveAdditionalPastRunDirectory(user, path + "a", out error), "The remove operation succeeded even though it should have failed!"); - Assert.AreEqual(1, previousRunDirectories.Count, "The previous run directory was removed!"); + Assert.HasCount(1, previousRunDirectories, "The previous run directory was removed!"); }); } @@ -625,14 +649,14 @@ public void AdditionalRunDirectoryPersistance() { CommandError error = null; var previousRunDirectories = project.AdditionalPreviousRunDirectories; - Assert.AreEqual(0, previousRunDirectories.Count, "There were already previous run directories!"); + Assert.IsEmpty(previousRunDirectories, "There were already previous run directories!"); Assert.IsTrue(project.AddAdditionalPastRunDirectory(user, path, out error), error?.Message ?? "Failed to have an error message!"); - Assert.AreEqual(1, previousRunDirectories.Count, "The previous runs did not include the new path!"); + Assert.HasCount(1, previousRunDirectories, "The previous runs did not include the new path!"); Assert.AreEqual(path, previousRunDirectories[0], "The path is not the same!"); }, (user, project) => { var previousRunDirectories = project.AdditionalPreviousRunDirectories; - Assert.AreEqual(1, previousRunDirectories.Count, "The number of past run directories is wrong after reloading!"); + Assert.HasCount(1, previousRunDirectories, "The number of past run directories is wrong after reloading!"); Assert.AreEqual(path, previousRunDirectories[0], "The path is not the same after reloading!"); }); } diff --git a/tests/XTMF2.UnitTests/TestUsers.cs b/tests/XTMF2.UnitTests/TestUsers.cs index 6635aca..e0b4b62 100644 --- a/tests/XTMF2.UnitTests/TestUsers.cs +++ b/tests/XTMF2.UnitTests/TestUsers.cs @@ -45,6 +45,23 @@ public void CreateUser() Assert.IsTrue(userController.Delete(user)); } + [TestMethod] + public void CreateUserOrGet() + { + var runtime = XTMFRuntime.CreateRuntime(); + var userController = runtime.UserController; + CommandError error = null; + const string userName = "NewUser"; + // ensure the user doesn't exist before we start + userController.Delete(userName); + Assert.IsTrue(userController.CreateOrGet(userName, false, out var user, out error)); + Assert.IsNull(error); + Assert.IsTrue(userController.CreateOrGet(userName, false, out var secondUser, out error)); + Assert.IsNotNull(user); + Assert.AreSame(user, secondUser); + Assert.IsTrue(userController.Delete(user)); + } + [TestMethod] public void UserPersistance() { @@ -88,20 +105,20 @@ public void AddUserToProject() Assert.IsTrue(projectController.CreateNewProject(user2, projectName2, out var session2, out error), error?.Message); // make sure we only have 1 project - Assert.AreEqual(1, user1.AvailableProjects.Count); - Assert.AreEqual(1, user2.AvailableProjects.Count); + Assert.HasCount(1, user1.AvailableProjects); + Assert.HasCount(1, user2.AvailableProjects); // Share project1 with user1 Assert.IsTrue(session1.ShareWith(user1, user2, out error), error?.Message); - Assert.AreEqual(1, user1.AvailableProjects.Count); - Assert.AreEqual(2, user2.AvailableProjects.Count); + Assert.HasCount(1, user1.AvailableProjects); + Assert.HasCount(2, user2.AvailableProjects); Assert.IsFalse(session1.ShareWith(user1, user2, out error), error?.Message); Assert.IsFalse(session1.ShareWith(user2, user2, out error), error?.Message); // Delete user1 and make sure that user2 loses reference to project1 Assert.IsTrue(userController.Delete(user1)); - Assert.AreEqual(1, user2.AvailableProjects.Count); + Assert.HasCount(1, user2.AvailableProjects); // finish cleaning up Assert.IsTrue(userController.Delete(user2)); @@ -127,19 +144,19 @@ public void AddUserToProjectTwice() Assert.IsTrue(projectController.CreateNewProject(user1, projectName1, out var session1, out error), error?.Message); // make sure we only have 1 project - Assert.AreEqual(1, user1.AvailableProjects.Count); - Assert.AreEqual(0, user2.AvailableProjects.Count); + Assert.HasCount(1, user1.AvailableProjects); + Assert.IsEmpty(user2.AvailableProjects); Assert.IsTrue(session1.ShareWith(user1, user2, out error)); - Assert.AreEqual(1, user2.AvailableProjects.Count); + Assert.HasCount(1, user2.AvailableProjects); // Ensure that the command fails to be added the second time Assert.IsFalse(session1.ShareWith(user1, user2, out error)); - Assert.AreEqual(1, user2.AvailableProjects.Count); + Assert.HasCount(1, user2.AvailableProjects); // Delete user1 and make sure that user2 loses reference to project1 Assert.IsTrue(userController.Delete(user1)); - Assert.AreEqual(0, user2.AvailableProjects.Count); + Assert.IsEmpty(user2.AvailableProjects); // finish cleaning up Assert.IsTrue(userController.Delete(user2)); @@ -167,13 +184,13 @@ public void RemoveUserFromProject() Assert.IsTrue(projectController.CreateNewProject(user2, projectName2, out var session2, out error), error?.Message); // make sure we only have 1 project - Assert.AreEqual(1, user1.AvailableProjects.Count); - Assert.AreEqual(1, user2.AvailableProjects.Count); + Assert.HasCount(1, user1.AvailableProjects); + Assert.HasCount(1, user2.AvailableProjects); // Share project1 with user1 Assert.IsTrue(session1.ShareWith(user1, user2, out error), error?.Message); - Assert.AreEqual(1, user1.AvailableProjects.Count); - Assert.AreEqual(2, user2.AvailableProjects.Count); + Assert.HasCount(1, user1.AvailableProjects); + Assert.HasCount(2, user2.AvailableProjects); Assert.IsFalse(session1.ShareWith(user1, user2, out error), error?.Message); Assert.IsFalse(session1.ShareWith(user2, user2, out error), error?.Message); @@ -182,12 +199,12 @@ public void RemoveUserFromProject() Assert.IsFalse(session1.RestrictAccess(user2, user1, out error)); Assert.IsTrue(session1.RestrictAccess(user1, user2, out error), error?.Message); - Assert.AreEqual(1, user1.AvailableProjects.Count); - Assert.AreEqual(1, user2.AvailableProjects.Count); + Assert.HasCount(1, user1.AvailableProjects); + Assert.HasCount(1, user2.AvailableProjects); // Delete user1 and make sure that user2 loses reference to project1 Assert.IsTrue(userController.Delete(user1)); - Assert.AreEqual(1, user2.AvailableProjects.Count); + Assert.HasCount(1, user2.AvailableProjects); // finish cleaning up Assert.IsTrue(userController.Delete(user2)); @@ -215,13 +232,13 @@ public void RemoveUserFromProjectTwice() Assert.IsTrue(projectController.CreateNewProject(user2, projectName2, out var session2, out error), error?.Message); // make sure we only have 1 project - Assert.AreEqual(1, user1.AvailableProjects.Count); - Assert.AreEqual(1, user2.AvailableProjects.Count); + Assert.HasCount(1, user1.AvailableProjects); + Assert.HasCount(1, user2.AvailableProjects); // Share project1 with user1 Assert.IsTrue(session1.ShareWith(user1, user2, out error), error?.Message); - Assert.AreEqual(1, user1.AvailableProjects.Count); - Assert.AreEqual(2, user2.AvailableProjects.Count); + Assert.HasCount(1, user1.AvailableProjects); + Assert.HasCount(2, user2.AvailableProjects); Assert.IsFalse(session1.ShareWith(user1, user2, out error), error?.Message); Assert.IsFalse(session1.ShareWith(user2, user2, out error), error?.Message); @@ -232,12 +249,12 @@ public void RemoveUserFromProjectTwice() // Ensure that we can't do it again Assert.IsFalse(session1.RestrictAccess(user1, user2, out error), error?.Message); - Assert.AreEqual(1, user1.AvailableProjects.Count); - Assert.AreEqual(1, user2.AvailableProjects.Count); + Assert.HasCount(1, user1.AvailableProjects); + Assert.HasCount(1, user2.AvailableProjects); // Delete user1 and make sure that user2 loses reference to project1 Assert.IsTrue(userController.Delete(user1)); - Assert.AreEqual(1, user2.AvailableProjects.Count); + Assert.HasCount(1, user2.AvailableProjects); // finish cleaning up Assert.IsTrue(userController.Delete(user2)); @@ -262,18 +279,18 @@ public void SwitchOwner() Assert.IsTrue(projectController.CreateNewProject(user1, projectName1, out var session1, out error), error?.Message); - Assert.AreEqual(1, user1.AvailableProjects.Count); - Assert.AreEqual(0, user2.AvailableProjects.Count); + Assert.HasCount(1, user1.AvailableProjects); + Assert.IsEmpty(user2.AvailableProjects); Assert.IsTrue(session1.SwitchOwner(user1, user2, out error), error?.Message); Assert.IsFalse(session1.SwitchOwner(user1, user2, out error)); - Assert.AreEqual(0, user1.AvailableProjects.Count); - Assert.AreEqual(1, user2.AvailableProjects.Count); + Assert.IsEmpty(user1.AvailableProjects); + Assert.HasCount(1, user2.AvailableProjects); Assert.IsTrue(userController.Delete(user1)); - Assert.AreEqual(1, user2.AvailableProjects.Count); + Assert.HasCount(1, user2.AvailableProjects); Assert.IsTrue(userController.Delete(user2)); } diff --git a/tests/XTMF2.UnitTests/TestXTMFRuntime.cs b/tests/XTMF2.UnitTests/TestXTMFRuntime.cs index 9b0bad0..be72d97 100644 --- a/tests/XTMF2.UnitTests/TestXTMFRuntime.cs +++ b/tests/XTMF2.UnitTests/TestXTMFRuntime.cs @@ -43,7 +43,7 @@ public void GetUserData() { XTMFRuntime runtime = XTMFRuntime.CreateRuntime(); var users = runtime.UserController.Users; - Assert.IsTrue(users.Count > 0); + Assert.IsNotEmpty(users); } [TestMethod] diff --git a/tests/XTMF2.UnitTests/XTMF2.UnitTests.csproj b/tests/XTMF2.UnitTests/XTMF2.UnitTests.csproj index 68bc033..5b1e733 100644 --- a/tests/XTMF2.UnitTests/XTMF2.UnitTests.csproj +++ b/tests/XTMF2.UnitTests/XTMF2.UnitTests.csproj @@ -1,15 +1,15 @@  - net6.0 + net10.0 XTMF2.UnitTests - + PreserveNewest - + PreserveNewest @@ -21,13 +21,13 @@ - - - + + + - + diff --git a/tests/XTMF2.UnitTests/assembly.cs b/tests/XTMF2.UnitTests/assembly.cs new file mode 100644 index 0000000..844265a --- /dev/null +++ b/tests/XTMF2.UnitTests/assembly.cs @@ -0,0 +1,21 @@ +/* + Copyright 2025 University of Toronto + + This file is part of XTMF2. + + XTMF2 is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + XTMF2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XTMF2. If not, see . +*/ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +[assembly: DoNotParallelize] \ No newline at end of file