From 163cd0ac327ffdec592653bb9cfde31261bc824c Mon Sep 17 00:00:00 2001 From: James Vaughan Date: Tue, 23 Nov 2021 10:07:04 -0500 Subject: [PATCH 01/16] Refactored argument null checks to use new methods. (#115) --- src/XTMF2/Controllers/ProjectController.cs | 73 ++--- src/XTMF2/Controllers/UserController.cs | 13 +- src/XTMF2/Editing/EditingStack.cs | 16 +- src/XTMF2/Editing/ModelSystemSession.cs | 294 +++++---------------- src/XTMF2/Editing/ProjectSession.cs | 151 +++-------- src/XTMF2/Helper.cs | 17 +- src/XTMF2/ModelSystemHeader.cs | 5 +- src/XTMF2/ProjectFile.cs | 10 +- 8 files changed, 159 insertions(+), 420 deletions(-) diff --git a/src/XTMF2/Controllers/ProjectController.cs b/src/XTMF2/Controllers/ProjectController.cs index 2af4b7f..8941572 100644 --- a/src/XTMF2/Controllers/ProjectController.cs +++ b/src/XTMF2/Controllers/ProjectController.cs @@ -74,10 +74,9 @@ public static ReadOnlyObservableCollection GetProjects(User user) /// True if the operation succeeds, false otherwise with an error message. public bool CreateNewProject(User owner, string name, out ProjectSession? session, 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)) { @@ -112,10 +111,7 @@ public bool CreateNewProject(User owner, string name, out ProjectSession? sessio public bool ImportProjectFile(User owner, string name, string filePath, out ProjectSession? session, out CommandError? error) { session = null; - if (owner is null) - { - throw new ArgumentNullException(nameof(owner)); - } + ArgumentNullException.ThrowIfNull(owner); if (string.IsNullOrWhiteSpace(name)) { @@ -165,14 +161,9 @@ public bool ImportProjectFile(User owner, string name, string filePath, out Proj /// 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) { - 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)) @@ -233,14 +224,9 @@ public bool GetProjectSession(User user, Project project, out ProjectSession? se /// 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) { - 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) { @@ -263,14 +249,9 @@ public bool GetProject(string userName, string projectName, out Project? project /// 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) { - 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); @@ -287,14 +268,9 @@ public bool GetProject(User user, string projectName, out Project? project, out /// True if the operation completes successfully, false otherwise with an error message. public bool DeleteProject(User user, string projectName, 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)); @@ -316,14 +292,9 @@ public bool DeleteProject(User user, string projectName, out CommandError? error /// True if the operation completes successfully, false otherwise with an error message. public bool DeleteProject(User owner, Project project, 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); @@ -429,10 +400,8 @@ public static bool ValidateProjectName(string name, out CommandError? error) /// public bool RenameProject(User user, Project project, string newProjectName, 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..019ff20 100644 --- a/src/XTMF2/Controllers/UserController.cs +++ b/src/XTMF2/Controllers/UserController.cs @@ -79,10 +79,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,14 +99,11 @@ 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 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..39c0df0 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 @@ -68,14 +68,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 +109,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 +147,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 +190,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 +239,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)); - } + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(boundary); + ArgumentNullException.ThrowIfNull(block); - if (boundary is null) - { - throw new ArgumentNullException(nameof(boundary)); - } - - if (block is null) - { - throw new ArgumentNullException(nameof(block)); - } lock (_sessionLock) { if (!_session.HasAccess(user)) @@ -305,15 +275,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 +311,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 +352,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 +461,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 +508,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 +547,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 +591,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) { @@ -778,14 +706,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 +741,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 +833,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 +878,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)) @@ -1008,14 +914,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 +950,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 +1025,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 +1067,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 +1154,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 +1198,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 +1243,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 +1264,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 +1284,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..ea8d5bd 100644 --- a/src/XTMF2/Editing/ProjectSession.cs +++ b/src/XTMF2/Editing/ProjectSession.cs @@ -118,10 +118,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); @@ -238,14 +236,9 @@ public bool CreateNewModelSystem(User user, string modelSystemName, out ModelSys /// True if the operation succeeds, false otherwise with an error message. public bool EditModelSystem(User user, ModelSystemHeader modelSystemHeader, out ModelSystemSession? session, 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); + session = null; lock (_sessionLock) { @@ -287,15 +280,8 @@ public bool EditModelSystem(User user, ModelSystemHeader modelSystemHeader, out /// True if the operation succeeds, false otherwise with an error message. public bool RemoveModelSystem(User user, ModelSystemHeader modelSystem, 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) { @@ -336,15 +322,8 @@ public bool RemoveModelSystem(User user, ModelSystemHeader modelSystem, out Comm /// True if the operation succeeds, false otherwise with an error message. public bool ExportProject(User user, string exportPath, 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) { @@ -372,15 +351,8 @@ public bool ExportProject(User user, string exportPath, out CommandError? error) /// True if the operation succeeds, false otherwise with error message. public bool ExportModelSystem(User user, ModelSystemHeader modelSystemHeader, string exportPath, 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)) { @@ -413,14 +385,9 @@ public bool ExportModelSystem(User user, ModelSystemHeader modelSystemHeader, st /// True if the operation succeeds, false otherwise with an error message. public bool GetModelSystemHeader(User user, string modelSystemName, out ModelSystemHeader? modelSystemHeader, 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 +404,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, 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 +420,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); } } @@ -482,14 +443,9 @@ 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) { - 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)) @@ -510,14 +466,9 @@ public bool SwitchOwner(User owner, User newOwner, out CommandError? error) /// True if the operation succeeds, false otherwise with an error message. public bool RestrictAccess(User owner, User toRestrict, 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)) @@ -547,20 +498,10 @@ public bool ImportModelSystem(User user, string modelSystemFilePath, string mode out ModelSystemHeader? header, 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); @@ -603,10 +544,7 @@ public bool ImportModelSystem(User user, string modelSystemFilePath, string mode /// True if the operation succeeds, false otherwise with an error message. public bool SetCustomRunDirectory(User user, string fullName, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } + ArgumentNullException.ThrowIfNull(user); if (string.IsNullOrWhiteSpace(fullName)) { @@ -632,10 +570,8 @@ public bool SetCustomRunDirectory(User user, string fullName, out CommandError? /// True if the operation succeeds, false otherwise with an error message. public bool ResetCustomRunDirectory(User user, out CommandError? error) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } + ArgumentNullException.ThrowIfNull(user); + lock (_sessionLock) { if (!Project.CanAccess(user)) @@ -657,14 +593,9 @@ public bool ResetCustomRunDirectory(User user, out CommandError? error) /// True if the operation succeeds, false otherwise with an error message. public bool RenameModelSystem(User user, ModelSystemHeader modelSystem, string newName, 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."); @@ -690,10 +621,8 @@ public bool RenameModelSystem(User user, ModelSystemHeader modelSystem, string n /// True if the operation succeeds, false otherwise with an error message. public bool AddAdditionalPastRunDirectory(User user, string pastRunDirectoryPath, 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."); @@ -719,10 +648,8 @@ public bool AddAdditionalPastRunDirectory(User user, string pastRunDirectoryPath /// True if the operation succeeds, false otherwise with an error message. public bool RemoveAdditionalPastRunDirectory(User user, string pastRunDirectoryPath, 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/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/ProjectFile.cs b/src/XTMF2/ProjectFile.cs index efbcafa..6d07836 100644 --- a/src/XTMF2/ProjectFile.cs +++ b/src/XTMF2/ProjectFile.cs @@ -117,14 +117,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 From 19c039e0efe4a90cfd6e84ab1978599ccf3d4d73 Mon Sep 17 00:00:00 2001 From: James Vaughan Date: Tue, 23 Nov 2021 12:03:04 -0500 Subject: [PATCH 02/16] Added CreateOrGet methods for Users, Projects, and Model Systems. (#116) Added static analysis hints on nullable attribute parameters. --- src/XTMF2/Controllers/ProjectController.cs | 67 ++++++++++++++----- src/XTMF2/Controllers/UserController.cs | 58 +++++++++++++--- src/XTMF2/Editing/ProjectSession.cs | 66 +++++++++++++----- src/XTMF2/ModelSystemConstruct/Boundary.cs | 3 +- .../ModelSystemConstruct/CommentBlock.cs | 3 +- .../ModelSystemConstruct/FunctionTemplate.cs | 3 +- src/XTMF2/ModelSystemConstruct/Link.cs | 3 +- src/XTMF2/ModelSystemConstruct/ModelSystem.cs | 19 ++++-- src/XTMF2/ModelSystemConstruct/Node.cs | 3 +- src/XTMF2/ModelSystemConstruct/Start.cs | 3 +- src/XTMF2/ModelSystemFile.cs | 13 ++-- src/XTMF2/Project.cs | 42 ++++++------ src/XTMF2/ProjectFile.cs | 5 +- src/XTMF2/Repository/ProjectRepository.cs | 3 +- tests/XTMF2.UnitTests/TestModelSystem.cs | 24 +++++++ tests/XTMF2.UnitTests/TestProjects.cs | 24 +++++++ tests/XTMF2.UnitTests/TestUsers.cs | 17 +++++ 17 files changed, 271 insertions(+), 85 deletions(-) diff --git a/src/XTMF2/Controllers/ProjectController.cs b/src/XTMF2/Controllers/ProjectController.cs index 8941572..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,7 +73,7 @@ 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) { ArgumentNullException.ThrowIfNull(owner); Helper.ThrowIfNullOrWhitespace(name); @@ -99,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. /// @@ -108,7 +144,7 @@ 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; ArgumentNullException.ThrowIfNull(owner); @@ -159,7 +195,7 @@ 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) { ArgumentNullException.ThrowIfNull(user); ArgumentNullException.ThrowIfNull(project); @@ -198,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 { @@ -222,7 +258,7 @@ 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) { Helper.ThrowIfNullOrWhitespace(userName); Helper.ThrowIfNullOrWhitespace(projectName); @@ -247,7 +283,7 @@ 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) { ArgumentNullException.ThrowIfNull(user); Helper.ThrowIfNullOrWhitespace(projectName); @@ -266,7 +302,7 @@ 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) { ArgumentNullException.ThrowIfNull(user); Helper.ThrowIfNullOrWhitespace(projectName); @@ -290,7 +326,7 @@ 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) { ArgumentNullException.ThrowIfNull(owner); ArgumentNullException.ThrowIfNull(project); @@ -316,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) @@ -347,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)) { @@ -369,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)) { @@ -398,7 +429,7 @@ 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) { ArgumentNullException.ThrowIfNull(user); diff --git a/src/XTMF2/Controllers/UserController.cs b/src/XTMF2/Controllers/UserController.cs index 019ff20..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 /// @@ -108,15 +146,15 @@ public bool Delete(User user) // 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); } @@ -172,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!); } @@ -187,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/ProjectSession.cs b/src/XTMF2/Editing/ProjectSession.cs index ea8d5bd..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 { @@ -180,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) { @@ -202,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)) @@ -226,6 +227,39 @@ public bool CreateNewModelSystem(User user, string modelSystemName, out ModelSys } } + /// + /// Create a new model system with the given name. The name must be unique. + /// + /// 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 CreateOrGetModelSystem(User user, string modelSystemName, [NotNullWhen(true)] out ModelSystemHeader? modelSystem, [NotNullWhen(false)] out CommandError? error) + { + modelSystem = null; + if (!ProjectController.ValidateProjectName(modelSystemName, out error)) + { + return false; + } + lock (_sessionLock) + { + 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. /// @@ -234,7 +268,7 @@ public bool CreateNewModelSystem(User user, string modelSystemName, out ModelSys /// 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, out ModelSystemSession? session, out CommandError? error) + public bool EditModelSystem(User user, ModelSystemHeader modelSystemHeader, [NotNullWhen(true)] out ModelSystemSession? session, [NotNullWhen(false)] out CommandError? error) { ArgumentNullException.ThrowIfNull(user); ArgumentNullException.ThrowIfNull(modelSystemHeader); @@ -278,7 +312,7 @@ 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) { ArgumentNullException.ThrowIfNull(user); ArgumentNullException.ThrowIfNull(modelSystem); @@ -320,7 +354,7 @@ 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) { ArgumentNullException.ThrowIfNull(user); Helper.ThrowIfNullOrWhitespace(exportPath); @@ -349,7 +383,7 @@ 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) { ArgumentNullException.ThrowIfNull(user); ArgumentNullException.ThrowIfNull(modelSystemHeader); @@ -383,7 +417,7 @@ 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) { ArgumentNullException.ThrowIfNull(user); Helper.ThrowIfNullOrWhitespace(modelSystemName); @@ -407,7 +441,7 @@ public bool GetModelSystemHeader(User user, string modelSystemName, out ModelSys /// 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 toShareWith, out CommandError? error) + public bool ShareWith(User doingShare, User toShareWith, [NotNullWhen(false)] out CommandError? error) { ArgumentNullException.ThrowIfNull(doingShare); ArgumentNullException.ThrowIfNull(toShareWith); @@ -441,7 +475,7 @@ 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) { ArgumentNullException.ThrowIfNull(owner); ArgumentNullException.ThrowIfNull(newOwner); @@ -464,7 +498,7 @@ 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) { ArgumentNullException.ThrowIfNull(owner); ArgumentNullException.ThrowIfNull(toRestrict); @@ -495,7 +529,7 @@ 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; ArgumentNullException.ThrowIfNull(user); @@ -542,7 +576,7 @@ 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) { ArgumentNullException.ThrowIfNull(user); @@ -568,7 +602,7 @@ 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) { ArgumentNullException.ThrowIfNull(user); @@ -591,7 +625,7 @@ 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) { ArgumentNullException.ThrowIfNull(user); ArgumentNullException.ThrowIfNull(modelSystem); @@ -619,7 +653,7 @@ 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) { ArgumentNullException.ThrowIfNull(user); @@ -646,7 +680,7 @@ 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) { ArgumentNullException.ThrowIfNull(user); diff --git a/src/XTMF2/ModelSystemConstruct/Boundary.cs b/src/XTMF2/ModelSystemConstruct/Boundary.cs index fc0502f..6636dbc 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; @@ -633,7 +634,7 @@ internal bool AddLink(Link link, out CommandError? e) internal bool Load(ModuleRepository modules, Dictionary typeLookup, Dictionary node, - ref Utf8JsonReader reader, ref string? error) + ref Utf8JsonReader reader, [NotNullWhen(false)] ref string? error) { if (reader.TokenType != JsonTokenType.StartObject) { 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/FunctionTemplate.cs b/src/XTMF2/ModelSystemConstruct/FunctionTemplate.cs index 657b7c6..08786b0 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; @@ -99,7 +100,7 @@ internal void Save(ref int index, Dictionary nodeDictionary, Dictiona /// 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) + ref Utf8JsonReader reader, Boundary parent, [NotNullWhen(true)] out FunctionTemplate? template, [NotNullWhen(false)] ref string? error) { template = null; string? name = null; 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..d4d34eb 100644 --- a/src/XTMF2/ModelSystemConstruct/ModelSystem.cs +++ b/src/XTMF2/ModelSystemConstruct/ModelSystem.cs @@ -27,6 +27,7 @@ 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; namespace XTMF2 { @@ -225,7 +226,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 +234,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 +273,7 @@ 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 { @@ -327,7 +328,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 +382,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) + 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, ref reader, ref error)) { return false; } diff --git a/src/XTMF2/ModelSystemConstruct/Node.cs b/src/XTMF2/ModelSystemConstruct/Node.cs index a2c47f5..d4f9648 100644 --- a/src/XTMF2/ModelSystemConstruct/Node.cs +++ b/src/XTMF2/ModelSystemConstruct/Node.cs @@ -23,6 +23,7 @@ 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; namespace XTMF2.ModelSystemConstruct { @@ -356,7 +357,7 @@ internal virtual void Save(ref int index, Dictionary moduleDictionary } internal static bool Load(ModuleRepository modules, Dictionary typeLookup, Dictionary nodes, - Boundary boundary, ref Utf8JsonReader reader, out Node? mss, ref string? error) + Boundary boundary, ref Utf8JsonReader reader, out Node? mss, [NotNullWhen(false)] ref string? error) { if (reader.TokenType != JsonTokenType.StartObject) { 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/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 6d07836..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; @@ -215,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 @@ -239,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/tests/XTMF2.UnitTests/TestModelSystem.cs b/tests/XTMF2.UnitTests/TestModelSystem.cs index 036f164..ab70fc5 100644 --- a/tests/XTMF2.UnitTests/TestModelSystem.cs +++ b/tests/XTMF2.UnitTests/TestModelSystem.cs @@ -67,6 +67,30 @@ public void ModelSystemPersistance() 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() { diff --git a/tests/XTMF2.UnitTests/TestProjects.cs b/tests/XTMF2.UnitTests/TestProjects.cs index 9146fff..4d1563a 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() { diff --git a/tests/XTMF2.UnitTests/TestUsers.cs b/tests/XTMF2.UnitTests/TestUsers.cs index 6635aca..ebfc7a3 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() { From a49b653a3a874c4f88668f5305ae28ede70d6e41 Mon Sep 17 00:00:00 2001 From: James Vaughan Date: Mon, 28 Feb 2022 16:27:31 -0500 Subject: [PATCH 03/16] Renamed XTMF2.Client to XTMF2.RunServer (#117) * Renamed XTMF2.Client to XTMF2.RunServer to better describe what it is used for. * Started to extract out parameters so they could be either a simple string value or become an expression. --- XTMF2.sln | 2 +- XTMF2.vsdx | Bin 129413 -> 0 bytes XTMF2Architecture.drawio | 1 + src/XTMF2.Client/Program.cs | 2 +- ...2.Client.csproj => XTMF2.RunServer.csproj} | 2 +- src/XTMF2/Bus/RunContext.cs | 12 ++-- .../Bus/{ClientBus.cs => RunServerBus.cs} | 6 +- src/XTMF2/Bus/Scheduler.cs | 4 +- src/XTMF2/Editing/ModelSystemSession.cs | 12 ++-- src/XTMF2/ModelSystemConstruct/Expression.cs | 52 +++++++++++++++ src/XTMF2/ModelSystemConstruct/Node.cs | 41 ++++++------ .../ParameterExpression.cs | 63 ++++++++++++++++++ .../Parameters/BasicParameter.cs | 60 +++++++++++++++++ .../Parameters/ScriptedParameter.cs | 47 +++++++++++++ tests/XTMF2.UnitTests/Editing/TestNode.cs | 12 ++-- tests/XTMF2.UnitTests/XTMF2.UnitTests.csproj | 6 +- 16 files changed, 274 insertions(+), 48 deletions(-) delete mode 100644 XTMF2.vsdx create mode 100644 XTMF2Architecture.drawio rename src/XTMF2.Client/{XTMF2.Client.csproj => XTMF2.RunServer.csproj} (95%) rename src/XTMF2/Bus/{ClientBus.cs => RunServerBus.cs} (98%) create mode 100644 src/XTMF2/ModelSystemConstruct/Expression.cs create mode 100644 src/XTMF2/ModelSystemConstruct/ParameterExpression.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/BasicParameter.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/ScriptedParameter.cs diff --git a/XTMF2.sln b/XTMF2.sln index 140646f..bd2988b 100644 --- a/XTMF2.sln +++ b/XTMF2.sln @@ -19,7 +19,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 730677aaf2d729e448b8a0f2c6fb70a175d89c57..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 129413 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 z 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 brbr6X@Q^VOB#98@&kkbyH#?AF5!C+yXm+_2 diff --git a/XTMF2Architecture.drawio b/XTMF2Architecture.drawio new file mode 100644 index 0000000..2ac90a6 --- /dev/null +++ b/XTMF2Architecture.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file 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 95% rename from src/XTMF2.Client/XTMF2.Client.csproj rename to src/XTMF2.Client/XTMF2.RunServer.csproj index 427b2a8..ed060c9 100644 --- a/src/XTMF2.Client/XTMF2.Client.csproj +++ b/src/XTMF2.Client/XTMF2.RunServer.csproj @@ -3,7 +3,7 @@ Exe net6.0 - XTMF2.Client + XTMF2.RunServer exe 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/Editing/ModelSystemSession.cs b/src/XTMF2/Editing/ModelSystemSession.cs index 39c0df0..27a0268 100644 --- a/src/XTMF2/Editing/ModelSystemSession.cs +++ b/src/XTMF2/Editing/ModelSystemSession.cs @@ -678,7 +678,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!), out var error) == true) { nodes.Add(child); if (boundary.AddLink(baseNode, hook, child, out var link, out error)) @@ -888,15 +889,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); + 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; } diff --git a/src/XTMF2/ModelSystemConstruct/Expression.cs b/src/XTMF2/ModelSystemConstruct/Expression.cs new file mode 100644 index 0000000..500eee5 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Expression.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.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace XTMF2.ModelSystemConstruct; + +///

+/// This class represents a calculation +/// +public class Expression +{ + private readonly string _expression; + + private Expression(string expression) + { + _expression = expression; + } + + /// + /// + /// + /// + /// + /// + /// + /// + public bool CreateExpression(IList nodes, string expresionText, [NotNullWhen(true)] out Expression? expression, [NotNullWhen(false)] ref string? error) + { + expression = null; + error = "Method not implemented!"; + return false; + } + + public string AsString() + { + return _expression; + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Node.cs b/src/XTMF2/ModelSystemConstruct/Node.cs index d4f9648..8dc4db8 100644 --- a/src/XTMF2/ModelSystemConstruct/Node.cs +++ b/src/XTMF2/ModelSystemConstruct/Node.cs @@ -62,7 +62,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 @@ -131,7 +131,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) @@ -139,9 +139,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))); @@ -210,22 +213,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(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; @@ -345,9 +346,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); + writer.WriteString(ParameterProperty, ParameterValue.GetRepresentation()); } if (IsDisabled) { @@ -369,7 +370,7 @@ internal static bool Load(ModuleRepository modules, Dictionary typeLo bool disabled = false; Rectangle point = new Rectangle(); string description = string.Empty; - string? parameter = null; + ParameterExpression? parameter = null; while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) { if (reader.TokenType == JsonTokenType.Comment) continue; @@ -424,7 +425,7 @@ internal static bool Load(ModuleRepository modules, Dictionary typeLo else if (reader.ValueTextEquals(ParameterProperty)) { reader.Read(); - parameter = reader.GetString() ?? string.Empty; + parameter = ParameterExpression.CreateParameter(reader.GetString() ?? string.Empty); } else if (reader.ValueTextEquals(DisabledProperty)) { diff --git a/src/XTMF2/ModelSystemConstruct/ParameterExpression.cs b/src/XTMF2/ModelSystemConstruct/ParameterExpression.cs new file mode 100644 index 0000000..e5f1b06 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/ParameterExpression.cs @@ -0,0 +1,63 @@ +/* + 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 XTMF2.ModelSystemConstruct.Parameters; + +namespace XTMF2.ModelSystemConstruct; + +/// +/// This class provides the interface for a Node to contain a value. +/// +public abstract class ParameterExpression +{ + + /// + /// 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. + internal abstract bool IsCompatible(Type type, ref string? errorString); + + /// + /// + /// + /// The type to try to extract. + /// An error message if the extraction fails. + /// + internal abstract object GetValue(Type type, ref string? errorString); + + /// + /// Gets a string based representation of the parameter + /// + /// + public abstract string GetRepresentation(); + + /// + /// Creates a parameter from a string value + /// + /// The string value of the parameter. + /// + internal static ParameterExpression CreateParameter(string value) + { + return new BasicParameter(value); + } + + + internal static ParameterExpression CreateParameter(Expression expression) + { + return new ScriptedParameter(expression); + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/BasicParameter.cs b/src/XTMF2/ModelSystemConstruct/Parameters/BasicParameter.cs new file mode 100644 index 0000000..17e5578 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/BasicParameter.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; + +/// +/// Provides the backing for a simple parameter +/// +internal class BasicParameter : ParameterExpression +{ + /// + /// The string presentation of the parameter + /// + private string _value; + + /// + /// Create a basic parameter + /// + /// The string value of the parameter. + public BasicParameter(string value) + { + _value = value; + } + + /// + public override string GetRepresentation() + { + return _value; + } + + /// + internal override bool IsCompatible(Type type, ref string? errorString) + { + return !ArbitraryParameterParser.Check(type, _value, ref errorString); + } + + internal override object GetValue(Type type, ref string? errorString) + { + var (sucess, value) = ArbitraryParameterParser.ArbitraryParameterParse(type, _value, ref errorString); + if (sucess) + { + errorString = null; + return value!; + } + return false; + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/ScriptedParameter.cs b/src/XTMF2/ModelSystemConstruct/Parameters/ScriptedParameter.cs new file mode 100644 index 0000000..2005042 --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/ScriptedParameter.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; + +internal class ScriptedParameter : ParameterExpression +{ + private Expression _expression; + + public ScriptedParameter(Expression expression) + { + _expression = expression; + } + + public override string GetRepresentation() + { + return _expression.AsString(); + } + + public override string? ToString() + { + return base.ToString(); + } + + internal override object GetValue(Type type, ref string? errorString) + { + throw new NotImplementedException(); + } + + internal override bool IsCompatible(Type type, ref string? errorString) + { + throw new NotImplementedException(); + } +} diff --git a/tests/XTMF2.UnitTests/Editing/TestNode.cs b/tests/XTMF2.UnitTests/Editing/TestNode.cs index f7fccd8..af916f3 100644 --- a/tests/XTMF2.UnitTests/Editing/TestNode.cs +++ b/tests/XTMF2.UnitTests/Editing/TestNode.cs @@ -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.GetRepresentation(), "The default value of the parameter was not 'Hello World'!"); break; } } @@ -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.GetRepresentation(), "The default value of the parameter was not 'Hello World'!"); break; } } @@ -429,7 +429,7 @@ 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.GetRepresentation(), "The default value of the parameter was not 'Hello World'!"); break; } } @@ -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.GetRepresentation(), "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.GetRepresentation(), "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.GetRepresentation(), "The unauthorized user changed the parameter's value!"); }); } diff --git a/tests/XTMF2.UnitTests/XTMF2.UnitTests.csproj b/tests/XTMF2.UnitTests/XTMF2.UnitTests.csproj index 68bc033..6c04b59 100644 --- a/tests/XTMF2.UnitTests/XTMF2.UnitTests.csproj +++ b/tests/XTMF2.UnitTests/XTMF2.UnitTests.csproj @@ -6,10 +6,10 @@ - + PreserveNewest - + PreserveNewest @@ -27,7 +27,7 @@ - + From 81cc7ee940f1cc4709a80aff82629ddaabf82edb Mon Sep 17 00:00:00 2001 From: James Vaughan Date: Wed, 16 Mar 2022 09:22:19 -0400 Subject: [PATCH 04/16] Added scripted parameter stub. (#118) --- XTMF2Architecture.drawio | 2 +- src/XTMF2/ArbitraryParameterParser.cs | 5 +- src/XTMF2/ModelSystemConstruct/Expression.cs | 26 ++++++- src/XTMF2/ModelSystemConstruct/Node.cs | 10 ++- .../ParameterExpression.cs | 35 +++++++--- .../Parameters/BasicParameter.cs | 9 +-- .../Parameters/ScriptedParameter.cs | 14 ++-- src/XTMF2/RuntimeModules/ScriptedParameter.cs | 69 +++++++++++++++++++ tests/XTMF2.UnitTests/Editing/TestNode.cs | 12 ++-- 9 files changed, 147 insertions(+), 35 deletions(-) create mode 100644 src/XTMF2/RuntimeModules/ScriptedParameter.cs diff --git a/XTMF2Architecture.drawio b/XTMF2Architecture.drawio index 2ac90a6..1f2a999 100644 --- a/XTMF2Architecture.drawio +++ b/XTMF2Architecture.drawio @@ -1 +1 @@ -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 \ No newline at end of file +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 \ No newline at end of file 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/ModelSystemConstruct/Expression.cs b/src/XTMF2/ModelSystemConstruct/Expression.cs index 500eee5..e4237e8 100644 --- a/src/XTMF2/ModelSystemConstruct/Expression.cs +++ b/src/XTMF2/ModelSystemConstruct/Expression.cs @@ -25,7 +25,7 @@ public class Expression { private readonly string _expression; - private Expression(string expression) + public Expression(string expression) { _expression = expression; } @@ -45,8 +45,32 @@ public bool CreateExpression(IList nodes, string expresionText, [NotNullWh return false; } + /// + /// Evaluate the expression and return the value. + /// + /// The nodes required to evaluate the expression. + /// The returned object from the evaluation, null if the expression can not be evaluated. + /// An error + /// True if the expression was evaluated correctly. + public bool Evaluate(IList nodes, [NotNullWhen(true)]out object? value, [NotNullWhen(false)] ref string? error) + { + throw new NotImplementedException(); + } + + /// + /// Gets the string representation of the expression. + /// + /// The string representation of the expression. public string AsString() { return _expression; } + + /// + /// The type that the expression will evaluate to. + /// + public Type Type + { + get => throw new NotImplementedException(); + } } diff --git a/src/XTMF2/ModelSystemConstruct/Node.cs b/src/XTMF2/ModelSystemConstruct/Node.cs index 8dc4db8..cd69b32 100644 --- a/src/XTMF2/ModelSystemConstruct/Node.cs +++ b/src/XTMF2/ModelSystemConstruct/Node.cs @@ -46,6 +46,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"; /// @@ -141,7 +142,7 @@ internal bool SetParameterValue(ParameterExpression? value, out CommandError? er string? errorString = null; if (value is not null) { - if (value.IsCompatible(Type.GenericTypeArguments[0], ref errorString)) + 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}!"); } @@ -348,7 +349,7 @@ internal virtual void Save(ref int index, Dictionary moduleDictionary writer.WriteNumber(IndexProperty, index++); if (ParameterValue is not null) { - writer.WriteString(ParameterProperty, ParameterValue.GetRepresentation()); + writer.WriteString(ParameterProperty, ParameterValue.Representation); } if (IsDisabled) { @@ -427,6 +428,11 @@ internal static bool Load(ModuleRepository modules, Dictionary typeLo reader.Read(); parameter = ParameterExpression.CreateParameter(reader.GetString() ?? string.Empty); } + else if(reader.ValueTextEquals(ParameterExpressionProperty)) + { + reader.Read(); + parameter = ParameterExpression.CreateParameter(new Expression(reader.GetString() ?? string.Empty)); + } else if (reader.ValueTextEquals(DisabledProperty)) { reader.Read(); diff --git a/src/XTMF2/ModelSystemConstruct/ParameterExpression.cs b/src/XTMF2/ModelSystemConstruct/ParameterExpression.cs index e5f1b06..b6b0f33 100644 --- a/src/XTMF2/ModelSystemConstruct/ParameterExpression.cs +++ b/src/XTMF2/ModelSystemConstruct/ParameterExpression.cs @@ -13,6 +13,8 @@ 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 XTMF2.ModelSystemConstruct.Parameters; namespace XTMF2.ModelSystemConstruct; @@ -20,44 +22,57 @@ namespace XTMF2.ModelSystemConstruct; /// /// This class provides the interface for a Node to contain a value. /// -public abstract class ParameterExpression +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. - internal abstract bool IsCompatible(Type type, ref string? errorString); + internal abstract bool IsCompatible(Type type, [NotNullWhen(false)] ref string? errorString); /// - /// + /// Tries to convert the parameter expression to the given type. /// /// The type to try to extract. /// An error message if the extraction fails. - /// - internal abstract object GetValue(Type type, ref string? errorString); + /// An object of the given type, or null with an error message if it fails. + internal abstract object? GetValue(Type type, ref string? errorString); /// /// Gets a string based representation of the parameter /// - /// - public abstract string GetRepresentation(); + public abstract string Representation { get; } /// /// Creates a parameter from a string value /// /// The string value of the parameter. - /// + /// A new parameter using the value. internal static ParameterExpression CreateParameter(string value) { return new BasicParameter(value); } - + /// + /// 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))); + } } diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/BasicParameter.cs b/src/XTMF2/ModelSystemConstruct/Parameters/BasicParameter.cs index 17e5578..6a4ee83 100644 --- a/src/XTMF2/ModelSystemConstruct/Parameters/BasicParameter.cs +++ b/src/XTMF2/ModelSystemConstruct/Parameters/BasicParameter.cs @@ -13,6 +13,7 @@ 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; @@ -36,15 +37,15 @@ public BasicParameter(string value) } /// - public override string GetRepresentation() + public override string Representation { - return _value; + get => _value; } /// - internal override bool IsCompatible(Type type, ref string? errorString) + internal override bool IsCompatible(Type type, [NotNullWhen(false)] ref string? errorString) { - return !ArbitraryParameterParser.Check(type, _value, ref errorString); + return ArbitraryParameterParser.Check(type, _value, ref errorString); } internal override object GetValue(Type type, ref string? errorString) diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/ScriptedParameter.cs b/src/XTMF2/ModelSystemConstruct/Parameters/ScriptedParameter.cs index 2005042..28408c5 100644 --- a/src/XTMF2/ModelSystemConstruct/Parameters/ScriptedParameter.cs +++ b/src/XTMF2/ModelSystemConstruct/Parameters/ScriptedParameter.cs @@ -13,6 +13,7 @@ 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; @@ -25,14 +26,9 @@ public ScriptedParameter(Expression expression) _expression = expression; } - public override string GetRepresentation() + public override string Representation { - return _expression.AsString(); - } - - public override string? ToString() - { - return base.ToString(); + get => _expression.AsString(); } internal override object GetValue(Type type, ref string? errorString) @@ -40,8 +36,8 @@ internal override object GetValue(Type type, ref string? errorString) throw new NotImplementedException(); } - internal override bool IsCompatible(Type type, ref string? errorString) + internal override bool IsCompatible(Type type, [NotNullWhen(false)] ref string? errorString) { - throw new NotImplementedException(); + return type.IsAssignableFrom(_expression.Type); } } diff --git a/src/XTMF2/RuntimeModules/ScriptedParameter.cs b/src/XTMF2/RuntimeModules/ScriptedParameter.cs new file mode 100644 index 0000000..5253d80 --- /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(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/tests/XTMF2.UnitTests/Editing/TestNode.cs b/tests/XTMF2.UnitTests/Editing/TestNode.cs index af916f3..cdf0d6e 100644 --- a/tests/XTMF2.UnitTests/Editing/TestNode.cs +++ b/tests/XTMF2.UnitTests/Editing/TestNode.cs @@ -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.GetRepresentation(), "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; } } @@ -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.GetRepresentation(), "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; } } @@ -429,7 +429,7 @@ 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.GetRepresentation(), "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; } } @@ -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.GetRepresentation(), "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.GetRepresentation(), "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.GetRepresentation(), "The unauthorized user changed the parameter's value!"); + Assert.AreEqual(parameterValue, basicParameter.ParameterValue.Representation, "The unauthorized user changed the parameter's value!"); }); } From d797b94c3909d9472d64ad944064454fc81e8740 Mon Sep 17 00:00:00 2001 From: James Vaughan Date: Thu, 17 Mar 2022 13:12:08 -0400 Subject: [PATCH 05/16] Implemented literals for parameter expressions. (#119) --- src/XTMF2/ModelSystemConstruct/Expression.cs | 58 ++--- src/XTMF2/ModelSystemConstruct/Node.cs | 18 +- .../ParameterExpression.cs | 3 +- .../Parameters/BasicParameter.cs | 2 +- .../Parameters/Compiler/CompilerException.cs | 27 +++ .../Parameters/Compiler/Literal.cs | 37 ++++ .../Compiler/Literals/BooleanLiteral.cs | 50 +++++ .../Compiler/Literals/FloatLiteral.cs | 50 +++++ .../Compiler/Literals/IntegerLiteral.cs | 50 +++++ .../Compiler/Literals/StringLiteral.cs | 44 ++++ .../Parameters/Compiler/ParameterCompiler.cs | 198 ++++++++++++++++++ .../Parameters/Compiler/Result.cs | 42 ++++ .../Compiler/Results/BooleanResult.cs | 52 +++++ .../Compiler/Results/ErrorResult.cs | 59 ++++++ .../Compiler/Results/FloatResult.cs | 52 +++++ .../Compiler/Results/IntegerResult.cs | 52 +++++ .../Compiler/Results/StringResult.cs | 52 +++++ .../Parameters/ScriptedParameter.cs | 22 +- src/XTMF2/RuntimeModules/ScriptedParameter.cs | 2 +- .../Parameters/Compiler/TestLiterals.cs | 162 ++++++++++++++ 20 files changed, 993 insertions(+), 39 deletions(-) create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/CompilerException.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literal.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/BooleanLiteral.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/FloatLiteral.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/IntegerLiteral.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/StringLiteral.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/ParameterCompiler.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Result.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Results/BooleanResult.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Results/ErrorResult.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Results/FloatResult.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Results/IntegerResult.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Results/StringResult.cs create mode 100644 tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLiterals.cs diff --git a/src/XTMF2/ModelSystemConstruct/Expression.cs b/src/XTMF2/ModelSystemConstruct/Expression.cs index e4237e8..b628c1c 100644 --- a/src/XTMF2/ModelSystemConstruct/Expression.cs +++ b/src/XTMF2/ModelSystemConstruct/Expression.cs @@ -15,62 +15,64 @@ You should have received a copy of the GNU General Public License using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; namespace XTMF2.ModelSystemConstruct; /// /// This class represents a calculation /// -public class Expression +public abstract class Expression { - private readonly string _expression; - - public Expression(string expression) - { - _expression = 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; /// /// /// - /// - /// /// - /// - /// - public bool CreateExpression(IList nodes, string expresionText, [NotNullWhen(true)] out Expression? expression, [NotNullWhen(false)] ref string? error) + /// + public Expression(string expression, int offset = 0) { - expression = null; - error = "Method not implemented!"; - return false; + Text = expression.AsMemory(); + Offset = offset; } /// - /// Evaluate the expression and return the value. + /// /// - /// The nodes required to evaluate the expression. - /// The returned object from the evaluation, null if the expression can not be evaluated. - /// An error - /// True if the expression was evaluated correctly. - public bool Evaluate(IList nodes, [NotNullWhen(true)]out object? value, [NotNullWhen(false)] ref string? error) + /// 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) { - throw new NotImplementedException(); + Text = expression; + Offset = offset; } /// /// Gets the string representation of the expression. /// /// The string representation of the expression. - public string AsString() + public ReadOnlySpan AsString() { - return _expression; + return Text.Span; } /// /// The type that the expression will evaluate to. /// - public Type Type - { - get => throw new NotImplementedException(); - } + public abstract Type Type { get; } + + /// + /// Gets a result with the literal's value. + /// + /// The result that this literal represents. + internal abstract Result GetResult(); } diff --git a/src/XTMF2/ModelSystemConstruct/Node.cs b/src/XTMF2/ModelSystemConstruct/Node.cs index cd69b32..935500b 100644 --- a/src/XTMF2/ModelSystemConstruct/Node.cs +++ b/src/XTMF2/ModelSystemConstruct/Node.cs @@ -24,6 +24,7 @@ You should have received a copy of the GNU General Public License using System.Linq; using XTMF2.Repository; using System.Diagnostics.CodeAnalysis; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; namespace XTMF2.ModelSystemConstruct { @@ -202,10 +203,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}!"); } @@ -214,7 +214,7 @@ internal bool ConstructModule(XTMFRuntime runtime, ref string? error) if (_type.IsConstructedGenericType && _type.GetGenericTypeDefinition() == GenericParameter) { var paramType = _type.GenericTypeArguments[0]; - var paramValue = ParameterValue?.GetValue(paramType, ref error); + var paramValue = ParameterValue?.GetValue(module, paramType, ref error); if (paramValue is not null) { if (!GenericValue.TryGetValue(_type, out var info)) @@ -431,7 +431,15 @@ internal static bool Load(ModuleRepository modules, Dictionary typeLo else if(reader.ValueTextEquals(ParameterExpressionProperty)) { reader.Read(); - parameter = ParameterExpression.CreateParameter(new Expression(reader.GetString() ?? string.Empty)); + var expressionString = reader.GetString() ?? string.Empty; + if (ParameterCompiler.CreateExpression(null!, expressionString, out var expression, ref error)) + { + parameter = ParameterExpression.CreateParameter(expression); + } + else + { + return FailWith(out mss, out error, $"Unable to process expression {expressionString}!\r\n{error}"); + } } else if (reader.ValueTextEquals(DisabledProperty)) { diff --git a/src/XTMF2/ModelSystemConstruct/ParameterExpression.cs b/src/XTMF2/ModelSystemConstruct/ParameterExpression.cs index b6b0f33..99377e3 100644 --- a/src/XTMF2/ModelSystemConstruct/ParameterExpression.cs +++ b/src/XTMF2/ModelSystemConstruct/ParameterExpression.cs @@ -35,10 +35,11 @@ public abstract class ParameterExpression : INotifyPropertyChanged /// /// 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. - internal abstract object? GetValue(Type type, ref string? errorString); + internal abstract object? GetValue(IModule caller, Type type, ref string? errorString); /// /// Gets a string based representation of the parameter diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/BasicParameter.cs b/src/XTMF2/ModelSystemConstruct/Parameters/BasicParameter.cs index 6a4ee83..0a67818 100644 --- a/src/XTMF2/ModelSystemConstruct/Parameters/BasicParameter.cs +++ b/src/XTMF2/ModelSystemConstruct/Parameters/BasicParameter.cs @@ -48,7 +48,7 @@ internal override bool IsCompatible(Type type, [NotNullWhen(false)] ref string? return ArbitraryParameterParser.Check(type, _value, ref errorString); } - internal override object GetValue(Type type, ref string? errorString) + internal override object GetValue(IModule caller, Type type, ref string? errorString) { var (sucess, value) = ArbitraryParameterParser.ArbitraryParameterParse(type, _value, ref errorString); if (sucess) 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..9caff12 --- /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() + { + 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..84fc527 --- /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() + { + 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..9a30821 --- /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() + { + 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..94ea421 --- /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() + { + return new StringResult(new string(Text.Span)); + } +} diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/ParameterCompiler.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/ParameterCompiler.cs new file mode 100644 index 0000000..c8f4ebe --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/ParameterCompiler.cs @@ -0,0 +1,198 @@ +/* + 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(); + 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 + { + 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(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 GetStringLiteral(ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + { + var span = text.Span; + expression = null; + int first = span.IndexOf('"'); + if(first == -1) + { + return false; + } + int second = span[(first + 1)..].IndexOf('"'); + if(second == -1) + { + throw new CompilerException("Unmatched string quote found!", offset + first); + } + // If there is anything after the second quote this is an invalid string literal. + // +2 skips the ending quote + if(IndexOfFirstNonWhiteSpace(span[(second + 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); + 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); + 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); + 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; + } + + private static int IndexOfFirstNonWhiteSpace(ReadOnlySpan text) + { + for (int i = 0; i < text.Length; i++) + { + if(!char.IsWhiteSpace(text[i])) + { + return i; + } + } + return -1; + } + + [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/ScriptedParameter.cs b/src/XTMF2/ModelSystemConstruct/Parameters/ScriptedParameter.cs index 28408c5..20ecc0a 100644 --- a/src/XTMF2/ModelSystemConstruct/Parameters/ScriptedParameter.cs +++ b/src/XTMF2/ModelSystemConstruct/Parameters/ScriptedParameter.cs @@ -14,6 +14,7 @@ You should have received a copy of the GNU General Public License */ using System; using System.Diagnostics.CodeAnalysis; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; namespace XTMF2.ModelSystemConstruct.Parameters; @@ -28,16 +29,31 @@ public ScriptedParameter(Expression expression) public override string Representation { - get => _expression.AsString(); + get => new (_expression.AsString()); } - internal override object GetValue(Type type, ref string? errorString) + internal override object? GetValue(IModule caller, Type type, ref string? errorString) { - throw new NotImplementedException(); + 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; } internal 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}!"; + } } diff --git a/src/XTMF2/RuntimeModules/ScriptedParameter.cs b/src/XTMF2/RuntimeModules/ScriptedParameter.cs index 5253d80..ad3297d 100644 --- a/src/XTMF2/RuntimeModules/ScriptedParameter.cs +++ b/src/XTMF2/RuntimeModules/ScriptedParameter.cs @@ -37,7 +37,7 @@ public override T Invoke() { Throw(error); } - var ret = Expression.GetValue(typeof(T), ref error); + var ret = Expression.GetValue(this, typeof(T), ref error); if (ret is null) { ThrowGotNull(); 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..3a95392 --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLiterals.cs @@ -0,0 +1,162 @@ +/* + 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; + +namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; + +[TestClass] +public class TestLiterals +{ + /// + /// Gives an empty set of nodes for use in the compiler. + /// + private static readonly List EmptyNodeList = new(); + + [TestMethod] + public void TestIntegerLiteral() + { + string error = null; + var text = "12345"; + Assert.IsTrue(ParameterCompiler.CreateExpression(EmptyNodeList, 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 TestBadIntegerLiteral() + { + TestInvalidScript("12345abc"); + } + + [TestMethod] + public void TestFloatLiteral() + { + string error = null; + var text = "12345.6"; + Assert.IsTrue(ParameterCompiler.CreateExpression(EmptyNodeList, 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 integer!"); + } + } + + [TestMethod] + public void TestBadFloatLiteral() + { + TestInvalidScript("1234abc.5"); + TestInvalidScript("1234.5f"); + TestInvalidScript("f1234.5"); + TestInvalidScript("1234.5 f"); + } + + [TestMethod] + public void TestBooleanLiteralTrue() + { + TestBooleanLiteral("True", true); + TestBooleanLiteral("true", true); + } + + [TestMethod] + public void TestBooleanLiteralFalse() + { + TestBooleanLiteral("False", false); + TestBooleanLiteral("false", false); + } + + /// + /// Provides a common call site for testing boolean literals. + /// + /// The text value to test. + /// The expected result of the text. + private static void TestBooleanLiteral(string text, bool expectedResult) + { + string error = null; + Assert.IsTrue(ParameterCompiler.CreateExpression(EmptyNodeList, text, out var expression, ref error), $"Failed to compile {text}"); + Assert.IsNotNull(expression, "The a null expression was returned!"); + Assert.AreEqual(typeof(bool), expression.Type); + Assert.IsTrue(ParameterCompiler.Evaluate(null!, expression, out var result, ref error), error); + if (result is bool boolResult) + { + Assert.AreEqual(expectedResult, boolResult); + } + else + { + Assert.Fail("The result is not an bool!"); + } + } + + /// + /// Make sure that the given script text fails to compile. + /// + /// + private static void TestInvalidScript(string text) + { + string error = null; + Assert.IsFalse(ParameterCompiler.CreateExpression(EmptyNodeList, text, out var expression, ref error), $"Invalid script was compiled: {text}"); + Assert.IsNull(expression); + Assert.IsNotNull(error); + } + + [TestMethod] + public void TestStringLiteral() + { + string error = null; + var text = "\"12345.6\""; + Assert.IsTrue(ParameterCompiler.CreateExpression(EmptyNodeList, 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 an integer!"); + } + } + + [TestMethod] + public void TestBadStringLiteral() + { + TestInvalidScript("\"No final quote"); + TestInvalidScript("Text before quote \""); + TestInvalidScript("\"Text\" Text after quote"); + TestInvalidScript("Text after quote \"Text\""); + } +} From 9ffd231259ed7994c9adac77064822c8fde264f8 Mon Sep 17 00:00:00 2001 From: James Vaughan Date: Thu, 17 Mar 2022 15:38:01 -0400 Subject: [PATCH 06/16] Implement Parameter Variables (#120) * Added an implementation for including variables. --- src/XTMF2/Editing/ModelSystemSession.cs | 4 +- src/XTMF2/ModelSystemConstruct/Expression.cs | 3 +- src/XTMF2/ModelSystemConstruct/Node.cs | 2 +- .../ParameterExpression.cs | 10 +- .../Parameters/BasicParameter.cs | 7 +- .../Compiler/Literals/BooleanLiteral.cs | 2 +- .../Compiler/Literals/FloatLiteral.cs | 2 +- .../Compiler/Literals/IntegerLiteral.cs | 2 +- .../Compiler/Literals/StringLiteral.cs | 2 +- .../Parameters/Compiler/ParameterCompiler.cs | 37 +++- .../Parameters/Compiler/Variable.cs | 47 +++++ .../Compiler/Variables/BooleanVariable.cs | 56 ++++++ .../Compiler/Variables/FloatVariable.cs | 56 ++++++ .../Compiler/Variables/IntegerVariable.cs | 56 ++++++ .../Compiler/Variables/StringVariable.cs | 56 ++++++ .../Parameters/ScriptedParameter.cs | 2 + .../Parameters/Compiler/TestVariables.cs | 189 ++++++++++++++++++ 17 files changed, 520 insertions(+), 13 deletions(-) create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Variable.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Variables/BooleanVariable.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Variables/FloatVariable.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Variables/IntegerVariable.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Variables/StringVariable.cs create mode 100644 tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestVariables.cs diff --git a/src/XTMF2/Editing/ModelSystemSession.cs b/src/XTMF2/Editing/ModelSystemSession.cs index 27a0268..815783a 100644 --- a/src/XTMF2/Editing/ModelSystemSession.cs +++ b/src/XTMF2/Editing/ModelSystemSession.cs @@ -679,7 +679,7 @@ void Remove() { var child = Node.Create(this.GetModuleRepository(), hook.Name, functionType, boundary, Rectangle.Hidden); - if (child?.SetParameterValue(ParameterExpression.CreateParameter(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)) @@ -890,7 +890,7 @@ public bool SetParameterValue(User user, Node basicParameter, string value, out return false; } var previousValue = basicParameter.ParameterValue; - var newValue = ParameterExpression.CreateParameter(value); + var newValue = ParameterExpression.CreateParameter(value, basicParameter.Type.GetGenericArguments()[0]); if (basicParameter.SetParameterValue(newValue, out error)) { Buffer.AddUndo(new Command(() => diff --git a/src/XTMF2/ModelSystemConstruct/Expression.cs b/src/XTMF2/ModelSystemConstruct/Expression.cs index b628c1c..3092ccc 100644 --- a/src/XTMF2/ModelSystemConstruct/Expression.cs +++ b/src/XTMF2/ModelSystemConstruct/Expression.cs @@ -74,5 +74,6 @@ public ReadOnlySpan AsString() /// Gets a result with the literal's value. /// /// The result that this literal represents. - internal abstract Result GetResult(); + /// The module that is asking for this expression to be resolved. + internal abstract Result GetResult(IModule caller); } diff --git a/src/XTMF2/ModelSystemConstruct/Node.cs b/src/XTMF2/ModelSystemConstruct/Node.cs index 935500b..aa538c4 100644 --- a/src/XTMF2/ModelSystemConstruct/Node.cs +++ b/src/XTMF2/ModelSystemConstruct/Node.cs @@ -426,7 +426,7 @@ internal static bool Load(ModuleRepository modules, Dictionary typeLo else if (reader.ValueTextEquals(ParameterProperty)) { reader.Read(); - parameter = ParameterExpression.CreateParameter(reader.GetString() ?? string.Empty); + parameter = ParameterExpression.CreateParameter(reader.GetString() ?? string.Empty, typeof(string)); } else if(reader.ValueTextEquals(ParameterExpressionProperty)) { diff --git a/src/XTMF2/ModelSystemConstruct/ParameterExpression.cs b/src/XTMF2/ModelSystemConstruct/ParameterExpression.cs index 99377e3..30cba26 100644 --- a/src/XTMF2/ModelSystemConstruct/ParameterExpression.cs +++ b/src/XTMF2/ModelSystemConstruct/ParameterExpression.cs @@ -46,14 +46,20 @@ public abstract class ParameterExpression : INotifyPropertyChanged /// 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) + internal static ParameterExpression CreateParameter(string value, Type type) { - return new BasicParameter(value); + return new BasicParameter(value, type); } /// diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/BasicParameter.cs b/src/XTMF2/ModelSystemConstruct/Parameters/BasicParameter.cs index 0a67818..3c9d081 100644 --- a/src/XTMF2/ModelSystemConstruct/Parameters/BasicParameter.cs +++ b/src/XTMF2/ModelSystemConstruct/Parameters/BasicParameter.cs @@ -27,13 +27,16 @@ internal class BasicParameter : ParameterExpression /// private string _value; + private readonly Type _type; + /// /// Create a basic parameter /// /// The string value of the parameter. - public BasicParameter(string value) + public BasicParameter(string value, Type type) { _value = value; + _type = type; } /// @@ -58,4 +61,6 @@ internal override object GetValue(IModule caller, Type type, ref string? errorSt } return false; } + + public override Type Type => _type; } diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/BooleanLiteral.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/BooleanLiteral.cs index 9caff12..c002074 100644 --- a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/BooleanLiteral.cs +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/BooleanLiteral.cs @@ -33,7 +33,7 @@ internal sealed class BooleanLiteral : Literal public BooleanLiteral(ReadOnlyMemory expression, int offset) : base(expression, offset) { } /// - internal override Result GetResult() + internal override Result GetResult(IModule caller) { if(bool.TryParse(Text.Span, out bool result)) { diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/FloatLiteral.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/FloatLiteral.cs index 84fc527..21bafce 100644 --- a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/FloatLiteral.cs +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/FloatLiteral.cs @@ -33,7 +33,7 @@ internal sealed class FloatLiteral : Literal public FloatLiteral(ReadOnlyMemory expression, int offset) : base(expression, offset) { } /// - internal override Result GetResult() + internal override Result GetResult(IModule caller) { if(float.TryParse(Text.Span, out var result)) { diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/IntegerLiteral.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/IntegerLiteral.cs index 9a30821..e8ec957 100644 --- a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/IntegerLiteral.cs +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/IntegerLiteral.cs @@ -33,7 +33,7 @@ internal sealed class IntegerLiteral : Literal public IntegerLiteral(ReadOnlyMemory expression, int offset) : base(expression, offset) { } /// - internal override Result GetResult() + internal override Result GetResult(IModule caller) { if(int.TryParse(Text.Span, out var result)) { diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/StringLiteral.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/StringLiteral.cs index 94ea421..72aae34 100644 --- a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/StringLiteral.cs +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/Literals/StringLiteral.cs @@ -37,7 +37,7 @@ public StringLiteral(ReadOnlyMemory expression, int offset) : base(express public override Type Type => typeof(string); /// - internal override Result GetResult() + internal override Result GetResult(IModule caller) { return new StringResult(new string(Text.Span)); } diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/ParameterCompiler.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/ParameterCompiler.cs index c8f4ebe..e3aa154 100644 --- a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/ParameterCompiler.cs +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/ParameterCompiler.cs @@ -34,7 +34,7 @@ public static class ParameterCompiler /// 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(); + var result = expression.GetResult(module); if (result.TryGetResult(out value, ref error)) { return true; @@ -67,7 +67,8 @@ public static bool CreateExpression(IList nodes, string expresionText, [No private static bool Compile(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) { - if(GetStringLiteral(text, offset, out expression) + if( 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) @@ -80,6 +81,38 @@ private static bool Compile(IList nodes, ReadOnlyMemory text, int of return false; } + 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); + 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; + } + else + { + return false; + } + } + private static bool GetStringLiteral(ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) { var span = text.Span; 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 index 20ecc0a..8e4a2c7 100644 --- a/src/XTMF2/ModelSystemConstruct/Parameters/ScriptedParameter.cs +++ b/src/XTMF2/ModelSystemConstruct/Parameters/ScriptedParameter.cs @@ -56,4 +56,6 @@ 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; } 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..2d8ef25 --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestVariables.cs @@ -0,0 +1,189 @@ +/* + 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; + +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.AreEqual(true, 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) => + { + string error = null; + var nodes = new List() + { + CreateNodeForVariable(session, user, "myStringVariable", "12345.6") + }; + TestFailToCompile(error, nodes, "myStringVariable asd"); + TestFailToCompile(error, nodes, "asd myStringVariable"); + TestFailToCompile(error, nodes, "wrongVariableName"); + }); + } + + private static void TestFailToCompile(string error, List nodes, string text) + { + Assert.IsFalse(ParameterCompiler.CreateExpression(nodes, text, out var expression, ref error), $"Succeeded to compile bad code: {text}"); + Assert.IsNull(expression); + Assert.IsNotNull(error); + } + + private 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; + } +} From 2173209fc7e16c89eeae48117321d81ed611f612 Mon Sep 17 00:00:00 2001 From: James Vaughan Date: Thu, 17 Mar 2022 16:02:39 -0400 Subject: [PATCH 07/16] Remove the architecture diagram from the XTMF2 repo. (#121) --- XTMF2.sln | 1 - XTMF2Architecture.drawio | 1 - 2 files changed, 2 deletions(-) delete mode 100644 XTMF2Architecture.drawio diff --git a/XTMF2.sln b/XTMF2.sln index bd2988b..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}" diff --git a/XTMF2Architecture.drawio b/XTMF2Architecture.drawio deleted file mode 100644 index 1f2a999..0000000 --- a/XTMF2Architecture.drawio +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file From abbbd8de8a940f2296e1ffdc8c799b712d9d4c1f Mon Sep 17 00:00:00 2001 From: James Vaughan Date: Fri, 18 Mar 2022 09:51:27 -0400 Subject: [PATCH 08/16] Mono operators (#122) * Implemented brackets and not. --- .../Compiler/MonoOperators/NotOperator.cs | 60 ++++++++ .../Parameters/Compiler/ParameterCompiler.cs | 87 ++++++++++- .../Parameters/Compiler/TestBracket.cs | 143 ++++++++++++++++++ .../Parameters/Compiler/TestCompilerHelper.cs | 99 ++++++++++++ .../Parameters/Compiler/TestLiterals.cs | 81 ++++++---- .../Parameters/Compiler/TestNotOperator.cs | 130 ++++++++++++++++ .../Parameters/Compiler/TestVariables.cs | 25 +-- 7 files changed, 573 insertions(+), 52 deletions(-) create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/MonoOperators/NotOperator.cs create mode 100644 tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestBracket.cs create mode 100644 tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestCompilerHelper.cs create mode 100644 tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestNotOperator.cs 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 index e3aa154..9fd0ff9 100644 --- a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/ParameterCompiler.cs +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/ParameterCompiler.cs @@ -67,7 +67,10 @@ public static bool CreateExpression(IList nodes, string expresionText, [No private static bool Compile(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) { - if( GetVariable(nodes, text, offset, out expression) + if( + 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) @@ -81,6 +84,84 @@ private static bool Compile(IList nodes, ReadOnlyMemory text, int of return false; } + 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) + { + return false; + } + if(span[first] != '!') + { + return false; + } + if(!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) + { + throw new CompilerException("Unmatched close bracket found!", offset + i); + } + 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) + { + return false; + } + if (second == -1) + { + throw new CompilerException("Unmatched bracket found!", offset + first); + } + // If there is anything after the second quote this is an invalid string literal. + if (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; @@ -118,7 +199,7 @@ private static bool GetStringLiteral(ReadOnlyMemory text, int offset, [Not var span = text.Span; expression = null; int first = span.IndexOf('"'); - if(first == -1) + if(first == -1 || first != IndexOfFirstNonWhiteSpace(span)) { return false; } @@ -129,7 +210,7 @@ private static bool GetStringLiteral(ReadOnlyMemory text, int offset, [Not } // If there is anything after the second quote this is an invalid string literal. // +2 skips the ending quote - if(IndexOfFirstNonWhiteSpace(span[(second + 2)..]) >= 0) + if(IndexOfFirstNonWhiteSpace(span[(second + first + 2)..]) >= 0) { return false; } 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..74d2e92 --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestBracket.cs @@ -0,0 +1,143 @@ +/* + 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; + +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() + { + string error = null; + var text = "(12345)"; + Assert.IsTrue(ParameterCompiler.CreateExpression(EmptyNodeList, 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); + } + } + + [TestMethod] + public void TestWhitespaceBeforeBracketIntegerLiteral() + { + string error = null; + var text = " (12345)"; + Assert.IsTrue(ParameterCompiler.CreateExpression(EmptyNodeList, 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); + } + } + + [TestMethod] + public void TestWhitespaceAfterBracketIntegerLiteral() + { + string error = null; + var text = "(12345) "; + Assert.IsTrue(ParameterCompiler.CreateExpression(EmptyNodeList, 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); + } + } + + [TestMethod] + public void TestDoubleBracketIntegerLiteral() + { + string error = null; + var text = "((12345))"; + Assert.IsTrue(ParameterCompiler.CreateExpression(EmptyNodeList, 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); + } + } + + [TestMethod] + public void TestTooManyOpenBrackets() + { + string error = null; + var text = "((12345)"; + Assert.IsFalse(ParameterCompiler.CreateExpression(EmptyNodeList, text, out var expression, ref error), $"Successfully compiled bad code: {text}"); + Assert.IsNull(expression); + Assert.IsNotNull(error); + } + + [TestMethod] + public void TestTooManyCloseBrackets() + { + string error = null; + var text = "(12345))"; + Assert.IsFalse(ParameterCompiler.CreateExpression(EmptyNodeList, text, out var expression, ref error), $"Successfully compiled bad code: {text}"); + Assert.IsNull(expression); + Assert.IsNotNull(error); + } + + [TestMethod] + public void TestTextBeforeBracket() + { + string error = null; + var text = "asd (12345)"; + Assert.IsFalse(ParameterCompiler.CreateExpression(EmptyNodeList, text, out var expression, ref error), $"Successfully compiled bad code: {text}"); + Assert.IsNull(expression); + Assert.IsNotNull(error); + } + + [TestMethod] + public void TestTextAfterBracket() + { + string error = null; + var text = "(12345) asd"; + Assert.IsFalse(ParameterCompiler.CreateExpression(EmptyNodeList, text, out var expression, ref error), $"Successfully compiled bad code: {text}"); + Assert.IsNull(expression); + Assert.IsNotNull(error); + } + + [TestMethod] + public void TestCloseBracketFirst() + { + string error = null; + var text = ")((12345)"; + Assert.IsFalse(ParameterCompiler.CreateExpression(EmptyNodeList, text, out var expression, ref error), $"Successfully compiled bad code: {text}"); + Assert.IsNull(expression); + Assert.IsNotNull(error); + } +} 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..e2cd1e3 --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestCompilerHelper.cs @@ -0,0 +1,99 @@ +/* + 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 boolean results. + /// + /// The text value to test. + /// The expected result of the text. + internal static void TestBoolean(string text, bool 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(bool), expression.Type); + Assert.IsTrue(ParameterCompiler.Evaluate(null!, expression, out var result, ref error), error); + if (result is bool boolResult) + { + Assert.AreEqual(expectedResult, boolResult); + } + else + { + Assert.Fail("The result is not an bool!"); + } + } + + /// + /// 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/TestLiterals.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLiterals.cs index 3a95392..e626a8d 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLiterals.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLiterals.cs @@ -21,16 +21,13 @@ You should have received a copy of the GNU General Public License 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 TestLiterals { - /// - /// Gives an empty set of nodes for use in the compiler. - /// - private static readonly List EmptyNodeList = new(); - [TestMethod] public void TestIntegerLiteral() { @@ -53,7 +50,7 @@ public void TestIntegerLiteral() [TestMethod] public void TestBadIntegerLiteral() { - TestInvalidScript("12345abc"); + TestFails("12345abc"); } [TestMethod] @@ -78,24 +75,24 @@ public void TestFloatLiteral() [TestMethod] public void TestBadFloatLiteral() { - TestInvalidScript("1234abc.5"); - TestInvalidScript("1234.5f"); - TestInvalidScript("f1234.5"); - TestInvalidScript("1234.5 f"); + TestFails("1234abc.5"); + TestFails("1234.5f"); + TestFails("f1234.5"); + TestFails("1234.5 f"); } [TestMethod] public void TestBooleanLiteralTrue() { - TestBooleanLiteral("True", true); - TestBooleanLiteral("true", true); + TestBoolean("True", true); + TestBoolean("true", true); } [TestMethod] public void TestBooleanLiteralFalse() { - TestBooleanLiteral("False", false); - TestBooleanLiteral("false", false); + TestBoolean("False", false); + TestBoolean("false", false); } /// @@ -120,23 +117,30 @@ private static void TestBooleanLiteral(string text, bool expectedResult) } } - /// - /// Make sure that the given script text fails to compile. - /// - /// - private static void TestInvalidScript(string text) + [TestMethod] + public void TestStringLiteral() { string error = null; - Assert.IsFalse(ParameterCompiler.CreateExpression(EmptyNodeList, text, out var expression, ref error), $"Invalid script was compiled: {text}"); - Assert.IsNull(expression); - Assert.IsNotNull(error); + var text = "\"12345.6\""; + Assert.IsTrue(ParameterCompiler.CreateExpression(EmptyNodeList, 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 TestStringLiteral() + public void TestWhitespaceBeforeStringLiteral() { string error = null; - var text = "\"12345.6\""; + var text = " \"12345.6\""; Assert.IsTrue(ParameterCompiler.CreateExpression(EmptyNodeList, text, out var expression, ref error), $"Failed to compile {text}"); Assert.IsNotNull(expression); Assert.AreEqual(typeof(string), expression.Type); @@ -147,16 +151,35 @@ public void TestStringLiteral() } else { - Assert.Fail("The result is not an integer!"); + Assert.Fail("The result is not a string!"); + } + } + + [TestMethod] + public void TestWhitespaceAfterStringLiteral() + { + string error = null; + var text = "\"12345.6\" "; + Assert.IsTrue(ParameterCompiler.CreateExpression(EmptyNodeList, 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 TestBadStringLiteral() { - TestInvalidScript("\"No final quote"); - TestInvalidScript("Text before quote \""); - TestInvalidScript("\"Text\" Text after quote"); - TestInvalidScript("Text after quote \"Text\""); + 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/TestNotOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestNotOperator.cs new file mode 100644 index 0000000..61036f8 --- /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() + { + TestBoolean("!True", false); + } + + + [TestMethod] + public void TestNotTrueInBrackets() + { + TestBoolean("!(True)", false); + } + + [TestMethod] + public void TestTextAfterFailsInBrackets() + { + TestFails("!(True asd)"); + } + + [TestMethod] + public void TestNotFalse() + { + TestBoolean("!False", true); + } + + [TestMethod] + public void TestNotTrueWithSpace() + { + TestBoolean("! True", false); + } + + [TestMethod] + public void TestNotFalseWithSpace() + { + TestBoolean("! False", true); + } + + [TestMethod] + public void TestNotTrueWithSpaceBefore() + { + TestBoolean(" !True", false); + } + + [TestMethod] + public void TestNotTrueWithSpaceAfter() + { + TestBoolean("!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") + }; + TestBoolean("!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/TestVariables.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestVariables.cs index 2d8ef25..573867e 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestVariables.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestVariables.cs @@ -20,6 +20,8 @@ You should have received a copy of the GNU General Public License using XTMF2.ModelSystemConstruct.Parameters.Compiler; using XTMF2.RuntimeModules; +using static XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler.TestCompilerHelper; + namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; [TestClass] @@ -160,30 +162,13 @@ public void TestBadVariableNames() { TestHelper.RunInModelSystemContext("TestBadVariableNames", (User user, ProjectSession project, ModelSystemSession session) => { - string error = null; var nodes = new List() { CreateNodeForVariable(session, user, "myStringVariable", "12345.6") }; - TestFailToCompile(error, nodes, "myStringVariable asd"); - TestFailToCompile(error, nodes, "asd myStringVariable"); - TestFailToCompile(error, nodes, "wrongVariableName"); + TestFails("myStringVariable asd", nodes); + TestFails("asd myStringVariable", nodes); + TestFails("wrongVariableName", nodes); }); } - - private static void TestFailToCompile(string error, List nodes, string text) - { - Assert.IsFalse(ParameterCompiler.CreateExpression(nodes, text, out var expression, ref error), $"Succeeded to compile bad code: {text}"); - Assert.IsNull(expression); - Assert.IsNotNull(error); - } - - private 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; - } } From 68a07783e9cbf039fbbec640ae010e9606c0345e Mon Sep 17 00:00:00 2001 From: James Vaughan Date: Mon, 21 Mar 2022 11:02:36 -0400 Subject: [PATCH 09/16] Implemented Select and Binary Operators (#123) * Implemented integer binary operators for +, -, *, /, <, <=, >, >=, ==, and !=. * Added Select operator. * The select operator acts like an if statement with (condition ? resultIfTrue : resultIfFalse). * Implemented And and Or operators for booleans. --- src/XTMF2/ModelSystemConstruct/Expression.cs | 11 - .../Compiler/BinaryOperators/AddOperator.cs | 124 +++++ .../Compiler/BinaryOperators/AndOperator.cs | 116 +++++ .../BinaryOperators/DivideOperator.cs | 120 +++++ .../BinaryOperators/EqualsOperator.cs | 124 +++++ .../Compiler/BinaryOperators/GreaterThan.cs | 120 +++++ .../BinaryOperators/GreaterThanOrEqual.cs | 120 +++++ .../BinaryOperators/LessThanOperator.cs | 120 +++++ .../BinaryOperators/LessThanOrEqual.cs | 120 +++++ .../BinaryOperators/MultiplyOperator.cs | 120 +++++ .../Compiler/BinaryOperators/NotEquals.cs | 124 +++++ .../Compiler/BinaryOperators/OrOperator.cs | 116 +++++ .../BinaryOperators/SubtractOperator.cs | 120 +++++ .../Parameters/Compiler/ParameterCompiler.cs | 436 ++++++++++++++++-- .../Parameters/Compiler/SelectOperator.cs | 119 +++++ .../Parameters/Compiler/TestAddOperator.cs | 114 +++++ .../Parameters/Compiler/TestAndOperator.cs | 77 ++++ .../Parameters/Compiler/TestBracket.cs | 93 ++-- .../Parameters/Compiler/TestCompilerHelper.cs | 22 +- .../Parameters/Compiler/TestDivideOperator.cs | 73 +++ .../Parameters/Compiler/TestEqualsOperator.cs | 100 ++++ .../Compiler/TestGreaterThanOperator.cs | 72 +++ .../TestGreaterThanOrEqualsOperator.cs | 73 +++ .../Compiler/TestLessThanOperator.cs | 72 +++ .../Compiler/TestLessThanOrEqualsOperator.cs | 73 +++ .../Parameters/Compiler/TestLiterals.cs | 116 +---- .../Compiler/TestMultiplyOperator.cs | 78 ++++ .../Compiler/TestNotEqualsOperator.cs | 100 ++++ .../Parameters/Compiler/TestNotOperator.cs | 16 +- .../Parameters/Compiler/TestOrOperator.cs | 83 ++++ .../Parameters/Compiler/TestSelectOperator.cs | 127 +++++ .../Compiler/TestSubtractOperator.cs | 66 +++ 32 files changed, 3134 insertions(+), 231 deletions(-) create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/AddOperator.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/AndOperator.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/DivideOperator.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/EqualsOperator.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/GreaterThan.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/GreaterThanOrEqual.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/LessThanOperator.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/LessThanOrEqual.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/MultiplyOperator.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/NotEquals.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/OrOperator.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/SubtractOperator.cs create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/SelectOperator.cs create mode 100644 tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAddOperator.cs create mode 100644 tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAndOperator.cs create mode 100644 tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestDivideOperator.cs create mode 100644 tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestEqualsOperator.cs create mode 100644 tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestGreaterThanOperator.cs create mode 100644 tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestGreaterThanOrEqualsOperator.cs create mode 100644 tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLessThanOperator.cs create mode 100644 tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLessThanOrEqualsOperator.cs create mode 100644 tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestMultiplyOperator.cs create mode 100644 tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestNotEqualsOperator.cs create mode 100644 tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestOrOperator.cs create mode 100644 tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestSelectOperator.cs create mode 100644 tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestSubtractOperator.cs diff --git a/src/XTMF2/ModelSystemConstruct/Expression.cs b/src/XTMF2/ModelSystemConstruct/Expression.cs index 3092ccc..e02e192 100644 --- a/src/XTMF2/ModelSystemConstruct/Expression.cs +++ b/src/XTMF2/ModelSystemConstruct/Expression.cs @@ -34,17 +34,6 @@ public abstract class Expression /// protected readonly int Offset; - /// - /// - /// - /// - /// - public Expression(string expression, int offset = 0) - { - Text = expression.AsMemory(); - Offset = offset; - } - /// /// /// 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..12f1e3f --- /dev/null +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/AddOperator.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 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 => _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 if(lhs is string ls && rhs is string rs) + { + return new StringResult(ls + rs); + } + else + { + return new ErrorResult($"Unknown type pair for addition, {_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(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) + { + if (lhs.Type != rhs.Type) + { + throw new CompilerException($"The LHS and RHS of the add 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/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/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/ParameterCompiler.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/ParameterCompiler.cs index 9fd0ff9..aeebb49 100644 --- a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/ParameterCompiler.cs +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/ParameterCompiler.cs @@ -55,9 +55,10 @@ public static bool CreateExpression(IList nodes, string expresionText, [No error = null; try { + ScanForInvalidBrackets(expresionText); return Compile(nodes, expresionText.AsMemory(), 0, out expression); } - catch(CompilerException exception) + catch (CompilerException exception) { error = exception.Message + $" Position: {exception.Position}"; expression = null; @@ -67,12 +68,25 @@ public static bool CreateExpression(IList nodes, string expresionText, [No private static bool Compile(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) { - if( - GetNot(nodes, text, offset, out 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) + || 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) + || GetBooleanLiteral(text, offset, out expression) || GetIntegerLiteral(text, offset, out expression) || GetFloatingPointLiteral(text, offset, out expression) ) @@ -81,27 +95,196 @@ private static bool Compile(IList nodes, ReadOnlyMemory text, int of } UnableToInterpret(text, offset); // This will never actually be executed - return false; + return false; } - private static bool GetNot(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) + private static bool GetSelect(IList nodes, ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) { expression = null; var span = text.Span; - var first = IndexOfFirstNonWhiteSpace(span); - if (first == -1) - { + var startOfCondition = IndexOfFirstNonWhiteSpace(span); + if (startOfCondition < 0) + { return false; } - if(span[first] != '!') + 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; } - if(!Compile(nodes, text[(first + 1)..], offset + first + 1, out var inner)) + 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 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)) + if (inner.Type != typeof(bool)) { throw new CompilerException($"Invalid expression type {inner.Type.FullName} passed into Not Operator!", first + offset); } @@ -118,7 +301,7 @@ private static bool GetBracket(IList nodes, ReadOnlyMemory text, int expression = null; for (int i = 0; i < span.Length; i++) { - if(span[i] == '(') + if (span[i] == '(') { if (first < 0) { @@ -126,35 +309,23 @@ private static bool GetBracket(IList nodes, ReadOnlyMemory text, int } bracketCount++; } - else if(span[i] == ')') + else if (span[i] == ')') { bracketCount--; - if(bracketCount < 0) - { - throw new CompilerException("Unmatched close bracket found!", offset + i); - } - if(bracketCount == 0) + 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])) + else if (first < 0 && !char.IsWhiteSpace(span[i])) { return false; } } - if(first == -1) - { - return false; - } - if (second == -1) - { - throw new CompilerException("Unmatched bracket found!", offset + first); - } - // If there is anything after the second quote this is an invalid string literal. - if (IndexOfFirstNonWhiteSpace(span[(second + 1)..]) >= 0) + if (first == -1 + || IndexOfFirstNonWhiteSpace(span[(second + 1)..]) >= 0) { return false; } @@ -167,6 +338,10 @@ private static bool GetVariable(IList nodes, ReadOnlyMemory text, in expression = null; var span = text.Span; var start = IndexOfFirstNonWhiteSpace(span); + if (start < 0) + { + return false; + } var end = start; for (; end < span.Length; end++) { @@ -188,10 +363,7 @@ private static bool GetVariable(IList nodes, ReadOnlyMemory text, in expression = Variable.CreateVariableForNode(node, innerText, offset + start); return true; } - else - { - return false; - } + return false; } private static bool GetStringLiteral(ReadOnlyMemory text, int offset, [NotNullWhen(true)] out Expression? expression) @@ -199,18 +371,14 @@ private static bool GetStringLiteral(ReadOnlyMemory text, int offset, [Not var span = text.Span; expression = null; int first = span.IndexOf('"'); - if(first == -1 || first != IndexOfFirstNonWhiteSpace(span)) + if (first == -1 || first != IndexOfFirstNonWhiteSpace(span)) { return false; } int second = span[(first + 1)..].IndexOf('"'); - if(second == -1) - { - throw new CompilerException("Unmatched string quote found!", offset + first); - } // 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) + if (IndexOfFirstNonWhiteSpace(span[(second + first + 2)..]) >= 0) { return false; } @@ -223,6 +391,10 @@ private static bool GetBooleanLiteral(ReadOnlyMemory text, int offset, [No expression = null; var span = text.Span; var start = IndexOfFirstNonWhiteSpace(span); + if (start < 0) + { + return false; + } var end = start; for (; end < span.Length; end++) { @@ -251,6 +423,10 @@ private static bool GetIntegerLiteral(ReadOnlyMemory text, int offset, [No expression = null; var span = text.Span; var start = IndexOfFirstNonWhiteSpace(span); + if (start < 0) + { + return false; + } var end = start; for (; end < span.Length; end++) { @@ -274,16 +450,20 @@ private static bool GetFloatingPointLiteral(ReadOnlyMemory text, int offse 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] == '.')) + 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 + if (end == start || (end < span.Length && IndexOfFirstNonWhiteSpace(span.Slice(end)) >= 0)) { return false; @@ -292,11 +472,50 @@ private static bool GetFloatingPointLiteral(ReadOnlyMemory text, int offse 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])) + 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; } @@ -304,6 +523,137 @@ private static int IndexOfFirstNonWhiteSpace(ReadOnlySpan text) 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) { 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/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAddOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAddOperator.cs new file mode 100644 index 0000000..3bba7f3 --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAddOperator.cs @@ -0,0 +1,114 @@ +/* + 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"); + } +} 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..6c5d637 --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAndOperator.cs @@ -0,0 +1,77 @@ +/* + 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); + } +} diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestBracket.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestBracket.cs index 74d2e92..d980ae5 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestBracket.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestBracket.cs @@ -21,6 +21,8 @@ You should have received a copy of the GNU General Public License using XTMF2.ModelSystemConstruct; using XTMF2.ModelSystemConstruct.Parameters.Compiler; +using static XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler.TestCompilerHelper; + namespace XTMF2.UnitTests.ModelSystemConstruct.Parameters.Compiler; [TestClass] @@ -34,110 +36,71 @@ public class TestBracket [TestMethod] public void TestBracketIntegerLiteral() { - string error = null; - var text = "(12345)"; - Assert.IsTrue(ParameterCompiler.CreateExpression(EmptyNodeList, 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); - } + TestExpression("(12345)", 12345); } [TestMethod] public void TestWhitespaceBeforeBracketIntegerLiteral() { - string error = null; - var text = " (12345)"; - Assert.IsTrue(ParameterCompiler.CreateExpression(EmptyNodeList, 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); - } + TestExpression(" (12345)", 12345); } [TestMethod] public void TestWhitespaceAfterBracketIntegerLiteral() { - string error = null; - var text = "(12345) "; - Assert.IsTrue(ParameterCompiler.CreateExpression(EmptyNodeList, 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); - } + TestExpression("(12345) ", 12345); } [TestMethod] public void TestDoubleBracketIntegerLiteral() { - string error = null; - var text = "((12345))"; - Assert.IsTrue(ParameterCompiler.CreateExpression(EmptyNodeList, 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); - } + TestExpression("((12345))", 12345); } [TestMethod] public void TestTooManyOpenBrackets() { - string error = null; - var text = "((12345)"; - Assert.IsFalse(ParameterCompiler.CreateExpression(EmptyNodeList, text, out var expression, ref error), $"Successfully compiled bad code: {text}"); - Assert.IsNull(expression); - Assert.IsNotNull(error); + TestFails("((12345)"); } [TestMethod] public void TestTooManyCloseBrackets() { - string error = null; - var text = "(12345))"; - Assert.IsFalse(ParameterCompiler.CreateExpression(EmptyNodeList, text, out var expression, ref error), $"Successfully compiled bad code: {text}"); - Assert.IsNull(expression); - Assert.IsNotNull(error); + TestFails("(12345))"); } [TestMethod] public void TestTextBeforeBracket() { - string error = null; - var text = "asd (12345)"; - Assert.IsFalse(ParameterCompiler.CreateExpression(EmptyNodeList, text, out var expression, ref error), $"Successfully compiled bad code: {text}"); - Assert.IsNull(expression); - Assert.IsNotNull(error); + TestFails("asd (12345)"); } [TestMethod] public void TestTextAfterBracket() { - string error = null; - var text = "(12345) asd"; - Assert.IsFalse(ParameterCompiler.CreateExpression(EmptyNodeList, text, out var expression, ref error), $"Successfully compiled bad code: {text}"); - Assert.IsNull(expression); - Assert.IsNotNull(error); + TestFails("(12345) asd"); } [TestMethod] public void TestCloseBracketFirst() { - string error = null; - var text = ")((12345)"; - Assert.IsFalse(ParameterCompiler.CreateExpression(EmptyNodeList, text, out var expression, ref error), $"Successfully compiled bad code: {text}"); - Assert.IsNull(expression); - Assert.IsNotNull(error); + 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 index e2cd1e3..916b62b 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestCompilerHelper.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestCompilerHelper.cs @@ -36,28 +36,36 @@ internal static class TestCompilerHelper internal static readonly List EmptyNodeList = new(); /// - /// Provides a common call site for testing boolean results. + /// 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 TestBoolean(string text, bool expectedResult, IList nodes = null) + 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.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(bool), expression.Type); + Assert.AreEqual(typeof(T), expression.Type); Assert.IsTrue(ParameterCompiler.Evaluate(null!, expression, out var result, ref error), error); - if (result is bool boolResult) + if (result is T res) { - Assert.AreEqual(expectedResult, boolResult); + 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 an bool!"); + Assert.Fail($"The result is not a {typeof(T).FullName}!"); } } 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..080c87f --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestDivideOperator.cs @@ -0,0 +1,73 @@ +/* + 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 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"); + } +} 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..e9f44c1 --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestEqualsOperator.cs @@ -0,0 +1,100 @@ +/* + 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); + } +} 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..5f1b9e2 --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestGreaterThanOperator.cs @@ -0,0 +1,72 @@ +/* + 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"); + } +} 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..d0ebca5 --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestGreaterThanOrEqualsOperator.cs @@ -0,0 +1,73 @@ +/* + 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"); + } +} 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..45caf68 --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLessThanOperator.cs @@ -0,0 +1,72 @@ +/* + 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"); + } +} 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..1021b9c --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLessThanOrEqualsOperator.cs @@ -0,0 +1,73 @@ +/* + 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"); + } +} diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLiterals.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLiterals.cs index e626a8d..683a0ff 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLiterals.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLiterals.cs @@ -17,9 +17,6 @@ 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; @@ -31,20 +28,7 @@ public class TestLiterals [TestMethod] public void TestIntegerLiteral() { - string error = null; - var text = "12345"; - Assert.IsTrue(ParameterCompiler.CreateExpression(EmptyNodeList, 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!"); - } + TestExpression("12345", 12345); } [TestMethod] @@ -56,20 +40,7 @@ public void TestBadIntegerLiteral() [TestMethod] public void TestFloatLiteral() { - string error = null; - var text = "12345.6"; - Assert.IsTrue(ParameterCompiler.CreateExpression(EmptyNodeList, 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 integer!"); - } + TestExpression("12345.6", 12345.6f); } [TestMethod] @@ -84,94 +55,45 @@ public void TestBadFloatLiteral() [TestMethod] public void TestBooleanLiteralTrue() { - TestBoolean("True", true); - TestBoolean("true", true); + TestExpression("True", true); + TestExpression("true", true); } [TestMethod] public void TestBooleanLiteralFalse() { - TestBoolean("False", false); - TestBoolean("false", false); + TestExpression("False", false); + TestExpression("false", false); } - /// - /// Provides a common call site for testing boolean literals. - /// - /// The text value to test. - /// The expected result of the text. - private static void TestBooleanLiteral(string text, bool expectedResult) + [TestMethod] + public void TestStringLiteral() { - string error = null; - Assert.IsTrue(ParameterCompiler.CreateExpression(EmptyNodeList, text, out var expression, ref error), $"Failed to compile {text}"); - Assert.IsNotNull(expression, "The a null expression was returned!"); - Assert.AreEqual(typeof(bool), expression.Type); - Assert.IsTrue(ParameterCompiler.Evaluate(null!, expression, out var result, ref error), error); - if (result is bool boolResult) - { - Assert.AreEqual(expectedResult, boolResult); - } - else - { - Assert.Fail("The result is not an bool!"); - } + TestExpression("\"12345.6\"", "12345.6"); } [TestMethod] - public void TestStringLiteral() + public void TestStringLiteralMissingLeft() + { + TestFails("12345.6\""); + } + + [TestMethod] + public void TestStringLiteralMissingRight() { - string error = null; - var text = "\"12345.6\""; - Assert.IsTrue(ParameterCompiler.CreateExpression(EmptyNodeList, 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!"); - } + TestFails("\"12345.6"); } [TestMethod] public void TestWhitespaceBeforeStringLiteral() { - string error = null; - var text = " \"12345.6\""; - Assert.IsTrue(ParameterCompiler.CreateExpression(EmptyNodeList, 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!"); - } + TestExpression(" \"12345.6\"", "12345.6"); } [TestMethod] public void TestWhitespaceAfterStringLiteral() { - string error = null; - var text = "\"12345.6\" "; - Assert.IsTrue(ParameterCompiler.CreateExpression(EmptyNodeList, 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!"); - } + TestExpression("\"12345.6\" ", "12345.6"); } [TestMethod] 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..18d56db --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestMultiplyOperator.cs @@ -0,0 +1,78 @@ +/* + 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 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"); + } +} 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..82cc19a --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestNotEqualsOperator.cs @@ -0,0 +1,100 @@ +/* + 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); + } +} diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestNotOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestNotOperator.cs index 61036f8..ca54f6f 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestNotOperator.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestNotOperator.cs @@ -33,14 +33,14 @@ public class TestNotOperator [TestMethod] public void TestNotTrue() { - TestBoolean("!True", false); + TestExpression("!True", false); } [TestMethod] public void TestNotTrueInBrackets() { - TestBoolean("!(True)", false); + TestExpression("!(True)", false); } [TestMethod] @@ -52,31 +52,31 @@ public void TestTextAfterFailsInBrackets() [TestMethod] public void TestNotFalse() { - TestBoolean("!False", true); + TestExpression("!False", true); } [TestMethod] public void TestNotTrueWithSpace() { - TestBoolean("! True", false); + TestExpression("! True", false); } [TestMethod] public void TestNotFalseWithSpace() { - TestBoolean("! False", true); + TestExpression("! False", true); } [TestMethod] public void TestNotTrueWithSpaceBefore() { - TestBoolean(" !True", false); + TestExpression(" !True", false); } [TestMethod] public void TestNotTrueWithSpaceAfter() { - TestBoolean("!True ", false); + TestExpression("!True ", false); } [TestMethod] @@ -106,7 +106,7 @@ public void TestNotVariable() { CreateNodeForVariable(session, user, "booleanVar", "true") }; - TestBoolean("!booleanVar", false, nodes); + TestExpression("!booleanVar", false, nodes); }); } 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..a7bac1a --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestOrOperator.cs @@ -0,0 +1,83 @@ +/* + 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); + } +} 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..3a5efec --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestSelectOperator.cs @@ -0,0 +1,127 @@ +/* + 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"); + } +} 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..0817dac --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestSubtractOperator.cs @@ -0,0 +1,66 @@ +/* + 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 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 -"); + } +} From 91a5f793036298ca1301ad4fa43df1939d89acf4 Mon Sep 17 00:00:00 2001 From: James Vaughan Date: Wed, 23 Mar 2022 10:52:18 -0400 Subject: [PATCH 10/16] Implemented Exponent Operator. (#124) * Implemented Exponent Operator. --- .../BinaryOperators/ExponentOperator.cs | 120 ++++++++++++++++++ .../Parameters/Compiler/ParameterCompiler.cs | 8 ++ .../Compiler/TestExponentOperator.cs | 80 ++++++++++++ 3 files changed, 208 insertions(+) create mode 100644 src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/ExponentOperator.cs create mode 100644 tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestExponentOperator.cs 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/ParameterCompiler.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/ParameterCompiler.cs index aeebb49..08f5e50 100644 --- a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/ParameterCompiler.cs +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/ParameterCompiler.cs @@ -82,6 +82,7 @@ private static bool Compile(IList nodes, ReadOnlyMemory text, int of || 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) @@ -203,6 +204,13 @@ private static bool GetDivide(IList nodes, ReadOnlyMemory text, int 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) ? 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..8fdcb79 --- /dev/null +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestExponentOperator.cs @@ -0,0 +1,80 @@ +/* + 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"); + } +} From d8c59a7762f53b8c98f19c20328b7cb593307a6a Mon Sep 17 00:00:00 2001 From: James Vaughan Date: Wed, 23 Mar 2022 11:28:46 -0400 Subject: [PATCH 11/16] Update readme to reference that we use .Net 6.0. (#125) Update CL/CI to compile Release builds. --- .github/workflows/blank.yml | 4 ++-- README.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/blank.yml b/.github/workflows/blank.yml index c8284f8..a256f41 100644 --- a/.github/workflows/blank.yml +++ b/.github/workflows/blank.yml @@ -13,7 +13,7 @@ jobs: dotnet-version: 6.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..7ffe208 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ operating XTMF2. ### Requirements -1. DotNet Core 3.0+ SDK +1. DotNet Core 6.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 From 2acc6f45f71f3a09dec6680eec2d591ea8337afa Mon Sep 17 00:00:00 2001 From: James Vaughan Date: Wed, 23 Mar 2022 12:36:08 -0400 Subject: [PATCH 12/16] Implemented additional unit tests to make sure that you can not accidentally mix types. (#126) --- .../Parameters/Compiler/TestAddOperator.cs | 73 ++++++++++++++++ .../Parameters/Compiler/TestAndOperator.cs | 72 ++++++++++++++++ .../Parameters/Compiler/TestDivideOperator.cs | 85 +++++++++++++++++++ .../Parameters/Compiler/TestEqualsOperator.cs | 72 ++++++++++++++++ .../Compiler/TestExponentOperator.cs | 72 ++++++++++++++++ .../Compiler/TestGreaterThanOperator.cs | 72 ++++++++++++++++ .../TestGreaterThanOrEqualsOperator.cs | 72 ++++++++++++++++ .../Compiler/TestLessThanOperator.cs | 72 ++++++++++++++++ .../Compiler/TestLessThanOrEqualsOperator.cs | 72 ++++++++++++++++ .../Compiler/TestMultiplyOperator.cs | 78 +++++++++++++++++ .../Compiler/TestNotEqualsOperator.cs | 72 ++++++++++++++++ .../Parameters/Compiler/TestOrOperator.cs | 72 ++++++++++++++++ .../Parameters/Compiler/TestSelectOperator.cs | 72 ++++++++++++++++ .../Compiler/TestSubtractOperator.cs | 78 +++++++++++++++++ 14 files changed, 1034 insertions(+) diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAddOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAddOperator.cs index 3bba7f3..21dffcf 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAddOperator.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAddOperator.cs @@ -111,4 +111,77 @@ public void TestAddInString() { TestExpression("\"1+2\"", "1+2"); } + + [TestMethod] + public void TestMixedIntFloatAdd() + { + TestFails("1.0 + 1"); + } + + [TestMethod] + public void TestMixedIntStrAdd() + { + TestFails("1 + \"Hello\""); + } + + [TestMethod] + public void TestMixedIntBooleanAdd() + { + TestFails("1 + true"); + } + + + [TestMethod] + public void TestMixedFloatIntAdd() + { + TestFails("1.0 + 1"); + } + + [TestMethod] + public void TestMixedFloatStrAdd() + { + TestFails("1.0 + \"Hello\""); + } + + [TestMethod] + public void TestMixedFloatBooleanAdd() + { + TestFails("1.0 + true"); + } + + [TestMethod] + public void TestMixedStrIntAdd() + { + TestFails("\"1\" + 1"); + } + + [TestMethod] + public void TestMixedStrFloatAdd() + { + TestFails("\"1\" + 1.0"); + } + + [TestMethod] + public void TestMixedStrBooleanAdd() + { + TestFails("\"1.0\" + true"); + } + + [TestMethod] + public void TestMixedBooleanIntAdd() + { + TestFails("true + 1"); + } + + [TestMethod] + public void TestMixedBooleanFloatAdd() + { + TestFails("true + 1.0"); + } + + [TestMethod] + public void TestMixedBooleanStrAdd() + { + TestFails("true + \"true\""); + } } diff --git a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAndOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAndOperator.cs index 6c5d637..7be2a6f 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAndOperator.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAndOperator.cs @@ -74,4 +74,76 @@ 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/TestDivideOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestDivideOperator.cs index 080c87f..d4172cb 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestDivideOperator.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestDivideOperator.cs @@ -35,6 +35,19 @@ public void TestDivide() 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() { @@ -70,4 +83,76 @@ 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 index e9f44c1..d9d8ced 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestEqualsOperator.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestEqualsOperator.cs @@ -97,4 +97,76 @@ 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 index 8fdcb79..fede6c8 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestExponentOperator.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestExponentOperator.cs @@ -77,4 +77,76 @@ 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 index 5f1b9e2..089977c 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestGreaterThanOperator.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestGreaterThanOperator.cs @@ -69,4 +69,76 @@ 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 index d0ebca5..b4060c4 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestGreaterThanOrEqualsOperator.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestGreaterThanOrEqualsOperator.cs @@ -70,4 +70,76 @@ 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 index 45caf68..98459b2 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLessThanOperator.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLessThanOperator.cs @@ -69,4 +69,76 @@ 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 index 1021b9c..6c72dee 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLessThanOrEqualsOperator.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestLessThanOrEqualsOperator.cs @@ -70,4 +70,76 @@ 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/TestMultiplyOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestMultiplyOperator.cs index 18d56db..a979bbb 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestMultiplyOperator.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestMultiplyOperator.cs @@ -34,6 +34,12 @@ public void TestMultiply() TestExpression("123 * 312", 38376); } + [TestMethod] + public void TestMultiplyFloat() + { + TestExpression("123.0 * 312.0", 38376.0f); + } + [TestMethod] public void TestMultipleMultiplications() { @@ -75,4 +81,76 @@ 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 index 82cc19a..4c573dd 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestNotEqualsOperator.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestNotEqualsOperator.cs @@ -97,4 +97,76 @@ 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/TestOrOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestOrOperator.cs index a7bac1a..5b9abb3 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestOrOperator.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestOrOperator.cs @@ -80,4 +80,76 @@ 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 index 3a5efec..2fee2b9 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestSelectOperator.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestSelectOperator.cs @@ -124,4 +124,76 @@ 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 index 0817dac..86c09a8 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestSubtractOperator.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestSubtractOperator.cs @@ -34,6 +34,12 @@ public void TestSubtract() TestExpression("123 - 312", -189); } + [TestMethod] + public void TestSubtractFloat() + { + TestExpression("123.0 - 312.0", -189.0f); + } + [TestMethod] public void TestMultipleSubtracts() { @@ -63,4 +69,76 @@ 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\""); + } } From 9f9ce1aa43a05695c87095929760152318921913 Mon Sep 17 00:00:00 2001 From: James Vaughan Date: Wed, 30 Mar 2022 12:10:57 -0400 Subject: [PATCH 13/16] Added the ability to add other variable types to strings. (#127) --- .../Compiler/BinaryOperators/AddOperator.cs | 41 ++++++++++++++----- .../Parameters/Compiler/TestAddOperator.cs | 12 +++--- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/AddOperator.cs b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/AddOperator.cs index 12f1e3f..0f277cb 100644 --- a/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/AddOperator.cs +++ b/src/XTMF2/ModelSystemConstruct/Parameters/Compiler/BinaryOperators/AddOperator.cs @@ -51,7 +51,7 @@ public AddOperator(Expression lhs, Expression rhs, ReadOnlyMemory expressi } /// - public override Type Type => _lhs.Type; + public override Type Type => _rhs.Type == typeof(string) ? typeof(string) : _lhs.Type; /// internal override Result GetResult(IModule caller) @@ -59,7 +59,11 @@ 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) + if(CheckForStringConversion(lhs, rhs, out var convertedString)) + { + return convertedString; + } + else if (lhs is int l && rhs is int r) { return new IntegerResult(l + r); } @@ -67,10 +71,6 @@ internal override Result GetResult(IModule caller) { return new FloatResult(lf + rf); } - else if(lhs is string ls && rhs is string rs) - { - return new StringResult(ls + rs); - } else { return new ErrorResult($"Unknown type pair for addition, {_lhs.Type.FullName} and {_rhs.Type.FullName}!", _lhs.Type); @@ -79,6 +79,26 @@ internal override Result GetResult(IModule caller) 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. /// @@ -87,7 +107,7 @@ internal override Result GetResult(IModule caller) /// 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) + private static bool GetResult(IModule caller, Expression child, [NotNullWhen(true)]out object? result, [NotNullWhen(false)] out Result? errorResult) { string? error = null; errorResult = null; @@ -112,11 +132,12 @@ private static bool GetResult(IModule caller, Expression child, out object? resu /// 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) + 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! LHS = {lhs.Type.FullName}, RHS = {rhs.Type.FullName}", offset); + 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(Array.IndexOf(_SupportedTypes, lhs.Type) < 0) + 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/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAddOperator.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAddOperator.cs index 21dffcf..7f1cb1a 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAddOperator.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestAddOperator.cs @@ -121,7 +121,7 @@ public void TestMixedIntFloatAdd() [TestMethod] public void TestMixedIntStrAdd() { - TestFails("1 + \"Hello\""); + TestExpression("1 + \"Hello\"", "1Hello"); } [TestMethod] @@ -140,7 +140,7 @@ public void TestMixedFloatIntAdd() [TestMethod] public void TestMixedFloatStrAdd() { - TestFails("1.0 + \"Hello\""); + TestExpression("1.2 + \"Hello\"", "1.2Hello"); } [TestMethod] @@ -152,19 +152,19 @@ public void TestMixedFloatBooleanAdd() [TestMethod] public void TestMixedStrIntAdd() { - TestFails("\"1\" + 1"); + TestExpression("\"1\" + 1", "11"); } [TestMethod] public void TestMixedStrFloatAdd() { - TestFails("\"1\" + 1.0"); + TestExpression("\"1\" + 1.1", "11.1"); } [TestMethod] public void TestMixedStrBooleanAdd() { - TestFails("\"1.0\" + true"); + TestExpression("\"1.0\" + true","1.0True"); } [TestMethod] @@ -182,6 +182,6 @@ public void TestMixedBooleanFloatAdd() [TestMethod] public void TestMixedBooleanStrAdd() { - TestFails("true + \"true\""); + TestExpression("true + \"true\"", "Truetrue"); } } From 2c3f13b9349d46af48f22d1f4898d29e4219dc6b Mon Sep 17 00:00:00 2001 From: James Vaughan Date: Mon, 18 Apr 2022 09:52:33 -0400 Subject: [PATCH 14/16] Implemented being able to create ScriptedParameters from the model system session. (#128) Currently this implementation does not support variables in the expression. --- src/XTMF2/Editing/ModelSystemSession.cs | 46 ++++++ src/XTMF2/ModelSystemConstruct/Boundary.cs | 8 +- .../ModelSystemConstruct/FunctionTemplate.cs | 4 +- src/XTMF2/ModelSystemConstruct/ModelSystem.cs | 24 ++- src/XTMF2/ModelSystemConstruct/Node.cs | 59 ++++++-- .../ParameterExpression.cs | 12 +- .../Parameters/BasicParameter.cs | 12 +- .../Parameters/ScriptedParameter.cs | 11 +- .../TestEditingParameterExpressions.cs | 139 ++++++++++++++++++ 9 files changed, 284 insertions(+), 31 deletions(-) create mode 100644 tests/XTMF2.UnitTests/Editing/TestEditingParameterExpressions.cs diff --git a/src/XTMF2/Editing/ModelSystemSession.cs b/src/XTMF2/Editing/ModelSystemSession.cs index 815783a..6e7452b 100644 --- a/src/XTMF2/Editing/ModelSystemSession.cs +++ b/src/XTMF2/Editing/ModelSystemSession.cs @@ -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; @@ -906,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. /// diff --git a/src/XTMF2/ModelSystemConstruct/Boundary.cs b/src/XTMF2/ModelSystemConstruct/Boundary.cs index 6636dbc..46e14f4 100644 --- a/src/XTMF2/ModelSystemConstruct/Boundary.cs +++ b/src/XTMF2/ModelSystemConstruct/Boundary.cs @@ -633,7 +633,7 @@ internal bool AddLink(Link link, out CommandError? e) - internal bool Load(ModuleRepository modules, Dictionary typeLookup, Dictionary node, + 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) @@ -693,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; } @@ -712,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; } @@ -765,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/FunctionTemplate.cs b/src/XTMF2/ModelSystemConstruct/FunctionTemplate.cs index 08786b0..84efe2b 100644 --- a/src/XTMF2/ModelSystemConstruct/FunctionTemplate.cs +++ b/src/XTMF2/ModelSystemConstruct/FunctionTemplate.cs @@ -99,7 +99,7 @@ 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, + 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; @@ -123,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/ModelSystem.cs b/src/XTMF2/ModelSystemConstruct/ModelSystem.cs index d4d34eb..6cff1ed 100644 --- a/src/XTMF2/ModelSystemConstruct/ModelSystem.cs +++ b/src/XTMF2/ModelSystemConstruct/ModelSystem.cs @@ -28,6 +28,7 @@ You should have received a copy of the GNU General Public License using XTMF2.Repository; using XTMF2.ModelSystemConstruct; using System.Diagnostics.CodeAnalysis; +using XTMF2.ModelSystemConstruct.Parameters.Compiler; namespace XTMF2 { @@ -83,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"; @@ -273,7 +279,8 @@ internal static bool Load(ProjectSession session, ModelSystemHeader modelSystemH } } - private static ModelSystem? Load(Stream rawStream, ModuleRepository modules, ModelSystemHeader modelSystemHeader, [NotNullWhen(false)] ref string? error) + private static ModelSystem? Load(Stream rawStream, ModuleRepository modules, ModelSystemHeader modelSystemHeader, + [NotNullWhen(false)] ref string? error) { try { @@ -283,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) @@ -296,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; } @@ -309,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) @@ -382,7 +398,7 @@ private static bool LoadTypes(Dictionary typeLookup, ref Utf8JsonRead } private static bool LoadBoundaries(ModuleRepository modules, Dictionary typeLookup, Dictionary nodes, - ref Utf8JsonReader reader, Boundary global, [NotNullWhen(false)] 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) { @@ -394,7 +410,7 @@ private static bool LoadBoundaries(ModuleRepository modules, Dictionary + /// 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) @@ -349,7 +381,7 @@ internal virtual void Save(ref int index, Dictionary moduleDictionary writer.WriteNumber(IndexProperty, index++); if (ParameterValue is not null) { - writer.WriteString(ParameterProperty, ParameterValue.Representation); + ParameterValue.Save(writer); } if (IsDisabled) { @@ -358,7 +390,7 @@ internal virtual void Save(ref int index, Dictionary moduleDictionary writer.WriteEndObject(); } - internal static bool Load(ModuleRepository modules, Dictionary typeLookup, Dictionary nodes, + 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) @@ -371,7 +403,8 @@ internal static bool Load(ModuleRepository modules, Dictionary typeLo bool disabled = false; Rectangle point = new Rectangle(); string description = string.Empty; - ParameterExpression? parameter = null; + ParameterExpression? basicParameter = null; + string? scriptedParameter = null; while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) { if (reader.TokenType == JsonTokenType.Comment) continue; @@ -426,20 +459,12 @@ internal static bool Load(ModuleRepository modules, Dictionary typeLo else if (reader.ValueTextEquals(ParameterProperty)) { reader.Read(); - parameter = ParameterExpression.CreateParameter(reader.GetString() ?? string.Empty, typeof(string)); + basicParameter = ParameterExpression.CreateParameter(reader.GetString() ?? string.Empty, typeof(string)); } - else if(reader.ValueTextEquals(ParameterExpressionProperty)) + else if (reader.ValueTextEquals(ParameterExpressionProperty)) { reader.Read(); - var expressionString = reader.GetString() ?? string.Empty; - if (ParameterCompiler.CreateExpression(null!, expressionString, out var expression, ref error)) - { - parameter = ParameterExpression.CreateParameter(expression); - } - else - { - return FailWith(out mss, out error, $"Unable to process expression {expressionString}!\r\n{error}"); - } + scriptedParameter = reader.GetString() ?? string.Empty; } else if (reader.ValueTextEquals(DisabledProperty)) { @@ -476,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 index 30cba26..c220376 100644 --- a/src/XTMF2/ModelSystemConstruct/ParameterExpression.cs +++ b/src/XTMF2/ModelSystemConstruct/ParameterExpression.cs @@ -15,6 +15,7 @@ You should have received a copy of the GNU General Public License using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.Text.Json; using XTMF2.ModelSystemConstruct.Parameters; namespace XTMF2.ModelSystemConstruct; @@ -30,7 +31,7 @@ public abstract class ParameterExpression : INotifyPropertyChanged /// 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. - internal abstract bool IsCompatible(Type type, [NotNullWhen(false)] ref string? errorString); + public abstract bool IsCompatible(Type type, [NotNullWhen(false)] ref string? errorString); /// /// Tries to convert the parameter expression to the given type. @@ -39,7 +40,7 @@ public abstract class ParameterExpression : INotifyPropertyChanged /// 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. - internal abstract object? GetValue(IModule caller, Type type, ref string? errorString); + public abstract object? GetValue(IModule caller, Type type, ref string? errorString); /// /// Gets a string based representation of the parameter @@ -82,4 +83,11 @@ 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 index 3c9d081..d83e49b 100644 --- a/src/XTMF2/ModelSystemConstruct/Parameters/BasicParameter.cs +++ b/src/XTMF2/ModelSystemConstruct/Parameters/BasicParameter.cs @@ -14,6 +14,7 @@ You should have received a copy of the GNU General Public License */ using System; using System.Diagnostics.CodeAnalysis; +using System.Text.Json; namespace XTMF2.ModelSystemConstruct.Parameters; @@ -22,6 +23,8 @@ namespace XTMF2.ModelSystemConstruct.Parameters; /// internal class BasicParameter : ParameterExpression { + protected const string ParameterProperty = "Parameter"; + /// /// The string presentation of the parameter /// @@ -46,12 +49,12 @@ public override string Representation } /// - internal override bool IsCompatible(Type type, [NotNullWhen(false)] ref string? errorString) + public override bool IsCompatible(Type type, [NotNullWhen(false)] ref string? errorString) { return ArbitraryParameterParser.Check(type, _value, ref errorString); } - internal override object GetValue(IModule caller, Type type, ref string? errorString) + public override object GetValue(IModule caller, Type type, ref string? errorString) { var (sucess, value) = ArbitraryParameterParser.ArbitraryParameterParse(type, _value, ref errorString); if (sucess) @@ -63,4 +66,9 @@ internal override object GetValue(IModule caller, Type type, ref string? errorSt } public override Type Type => _type; + + internal override void Save(Utf8JsonWriter writer) + { + writer.WriteString(ParameterProperty, Representation); + } } diff --git a/src/XTMF2/ModelSystemConstruct/Parameters/ScriptedParameter.cs b/src/XTMF2/ModelSystemConstruct/Parameters/ScriptedParameter.cs index 8e4a2c7..4b9c1b8 100644 --- a/src/XTMF2/ModelSystemConstruct/Parameters/ScriptedParameter.cs +++ b/src/XTMF2/ModelSystemConstruct/Parameters/ScriptedParameter.cs @@ -14,12 +14,14 @@ You should have received a copy of the GNU General Public License */ 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) @@ -32,7 +34,7 @@ public override string Representation get => new (_expression.AsString()); } - internal override object? GetValue(IModule caller, Type type, ref string? errorString) + public override object? GetValue(IModule caller, Type type, ref string? errorString) { if(_expression.Type != type) { @@ -46,7 +48,7 @@ public override string Representation return null; } - internal override bool IsCompatible(Type type, [NotNullWhen(false)] ref string? errorString) + public override bool IsCompatible(Type type, [NotNullWhen(false)] ref string? errorString) { return type.IsAssignableFrom(_expression.Type); } @@ -58,4 +60,9 @@ private static string ThrowInvalidTypes(Type expected, Type expressionType) } public override Type Type => _expression.Type; + + internal override void Save(Utf8JsonWriter writer) + { + writer.WriteString(ParameterExpressionProperty, Representation); + } } diff --git a/tests/XTMF2.UnitTests/Editing/TestEditingParameterExpressions.cs b/tests/XTMF2.UnitTests/Editing/TestEditingParameterExpressions.cs new file mode 100644 index 0000000..2463918 --- /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(childNode.ParameterValue.Type, typeof(string)); + 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(childNode.ParameterValue.Type, typeof(string)); + 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(childNode.ParameterValue.Type, typeof(string)); + 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(childNode.ParameterValue.Type, typeof(string)); + 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)); + }); + } +} + From e55cf8f421c2fd54aa34331194e34b1b13e0206b Mon Sep 17 00:00:00 2001 From: James Vaughan Date: Mon, 20 Nov 2023 09:17:12 -0500 Subject: [PATCH 15/16] Update .Net to Version 8 (#137) * Update to .Net 8 * Update CL/CI compiler --- .github/workflows/blank.yml | 2 +- src/XTMF2.Client/XTMF2.RunServer.csproj | 2 +- src/XTMF2.Interfaces/XTMF2.Interfaces.csproj | 2 +- src/XTMF2.Run/XTMF2.Run.csproj | 2 +- src/XTMF2/XTMF2.csproj | 2 +- tests/XTMF2.UnitTests/XTMF2.UnitTests.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/blank.yml b/.github/workflows/blank.yml index a256f41..f1b21de 100644 --- a/.github/workflows/blank.yml +++ b/.github/workflows/blank.yml @@ -10,7 +10,7 @@ jobs: - name: Setup .NetCore uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.100 + dotnet-version: 8.0.100 - name: Build with dotnet run: | dotnet build -c Release diff --git a/src/XTMF2.Client/XTMF2.RunServer.csproj b/src/XTMF2.Client/XTMF2.RunServer.csproj index ed060c9..3225764 100644 --- a/src/XTMF2.Client/XTMF2.RunServer.csproj +++ b/src/XTMF2.Client/XTMF2.RunServer.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 XTMF2.RunServer exe diff --git a/src/XTMF2.Interfaces/XTMF2.Interfaces.csproj b/src/XTMF2.Interfaces/XTMF2.Interfaces.csproj index fbd674c..3a032f7 100644 --- a/src/XTMF2.Interfaces/XTMF2.Interfaces.csproj +++ b/src/XTMF2.Interfaces/XTMF2.Interfaces.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 XTMF2 enable diff --git a/src/XTMF2.Run/XTMF2.Run.csproj b/src/XTMF2.Run/XTMF2.Run.csproj index 44f234a..946a5b4 100644 --- a/src/XTMF2.Run/XTMF2.Run.csproj +++ b/src/XTMF2.Run/XTMF2.Run.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 enable diff --git a/src/XTMF2/XTMF2.csproj b/src/XTMF2/XTMF2.csproj index 0cb7908..f1e088f 100644 --- a/src/XTMF2/XTMF2.csproj +++ b/src/XTMF2/XTMF2.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 true https://github.com/TravelModellingGroup/XTMF2.git diff --git a/tests/XTMF2.UnitTests/XTMF2.UnitTests.csproj b/tests/XTMF2.UnitTests/XTMF2.UnitTests.csproj index 6c04b59..9369da3 100644 --- a/tests/XTMF2.UnitTests/XTMF2.UnitTests.csproj +++ b/tests/XTMF2.UnitTests/XTMF2.UnitTests.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 XTMF2.UnitTests From 438c95ee08f11bbb5eafdf877ff3db0dc3f9b9af Mon Sep 17 00:00:00 2001 From: James Vaughan Date: Wed, 12 Nov 2025 10:11:46 -0500 Subject: [PATCH 16/16] .Net 10 (#138) * Updated to .Net 10 Removed API breaking changes for UnitTest framework * Updated unit tests to remove all warnings. * Updated workflow to use Windows 2025 --- .github/workflows/blank.yml | 4 +- README.md | 2 +- src/XTMF2.Client/XTMF2.RunServer.csproj | 3 +- src/XTMF2.Interfaces/XTMF2.Interfaces.csproj | 2 +- src/XTMF2.Run/XTMF2.Run.csproj | 2 +- src/XTMF2/XTMF2.csproj | 8 +- .../XTMF2.UnitTests/Editing/TestBoundaries.cs | 68 +++---- .../Editing/TestCommentBlock.cs | 52 +++--- .../TestEditingParameterExpressions.cs | 8 +- .../XTMF2.UnitTests/Editing/TestFunctions.cs | 34 ++-- tests/XTMF2.UnitTests/Editing/TestLinks.cs | 104 +++++------ tests/XTMF2.UnitTests/Editing/TestNode.cs | 166 +++++++++--------- .../Parameters/Compiler/TestVariables.cs | 2 +- tests/XTMF2.UnitTests/TestModelSystem.cs | 26 +-- tests/XTMF2.UnitTests/TestProjects.cs | 46 ++--- tests/XTMF2.UnitTests/TestUsers.cs | 58 +++--- tests/XTMF2.UnitTests/TestXTMFRuntime.cs | 2 +- tests/XTMF2.UnitTests/XTMF2.UnitTests.csproj | 8 +- tests/XTMF2.UnitTests/assembly.cs | 21 +++ 19 files changed, 315 insertions(+), 301 deletions(-) create mode 100644 tests/XTMF2.UnitTests/assembly.cs diff --git a/.github/workflows/blank.yml b/.github/workflows/blank.yml index f1b21de..47fac2e 100644 --- a/.github/workflows/blank.yml +++ b/.github/workflows/blank.yml @@ -4,13 +4,13 @@ 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: 8.0.100 + dotnet-version: 10.0.100 - name: Build with dotnet run: | dotnet build -c Release diff --git a/README.md b/README.md index 7ffe208..4d13578 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ operating XTMF2. ### Requirements -1. DotNet Core 6.0+ SDK +1. DotNet Core 10.0+ SDK ### Clone the XTMF2 repository diff --git a/src/XTMF2.Client/XTMF2.RunServer.csproj b/src/XTMF2.Client/XTMF2.RunServer.csproj index 3225764..fe96c57 100644 --- a/src/XTMF2.Client/XTMF2.RunServer.csproj +++ b/src/XTMF2.Client/XTMF2.RunServer.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + 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 3a032f7..6bd0260 100644 --- a/src/XTMF2.Interfaces/XTMF2.Interfaces.csproj +++ b/src/XTMF2.Interfaces/XTMF2.Interfaces.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 XTMF2 enable diff --git a/src/XTMF2.Run/XTMF2.Run.csproj b/src/XTMF2.Run/XTMF2.Run.csproj index 946a5b4..ca3d18a 100644 --- a/src/XTMF2.Run/XTMF2.Run.csproj +++ b/src/XTMF2.Run/XTMF2.Run.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 enable diff --git a/src/XTMF2/XTMF2.csproj b/src/XTMF2/XTMF2.csproj index f1e088f..7dca667 100644 --- a/src/XTMF2/XTMF2.csproj +++ b/src/XTMF2/XTMF2.csproj @@ -1,7 +1,7 @@  - net8.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 index 2463918..6bf9719 100644 --- a/tests/XTMF2.UnitTests/Editing/TestEditingParameterExpressions.cs +++ b/tests/XTMF2.UnitTests/Editing/TestEditingParameterExpressions.cs @@ -46,7 +46,7 @@ public void ParameterExpression() Assert.IsNotNull(childNode); Assert.IsTrue(mSession.SetParameterExpression(user, childNode, "\"Hello World\" + (1 + 2)", out error)); Assert.IsNotNull(childNode.ParameterValue); - Assert.AreEqual(childNode.ParameterValue.Type, typeof(string)); + 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)); }); @@ -69,7 +69,7 @@ public void ParameterExpressionUndo() 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(childNode.ParameterValue.Type, typeof(string)); + 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); @@ -94,7 +94,7 @@ public void ParameterExpressionRedo() 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(childNode.ParameterValue.Type, typeof(string)); + 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); @@ -120,7 +120,7 @@ public void ParameterExpressionSaved() Assert.IsNotNull(childNode); Assert.IsTrue(mSession.SetParameterExpression(user, childNode, "\"Hello World\" + (1 + 2)", out error)); Assert.IsNotNull(childNode.ParameterValue); - Assert.AreEqual(childNode.ParameterValue.Type, typeof(string)); + 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)=> 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 cdf0d6e..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++) @@ -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++) @@ -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++) @@ -435,8 +435,8 @@ public void RemoveNodeWithParameterGenerationWithBadUser() } 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); }); } @@ -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/TestVariables.cs b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestVariables.cs index 573867e..59c850b 100644 --- a/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestVariables.cs +++ b/tests/XTMF2.UnitTests/ModelSystemConstruct/Parameters/Compiler/TestVariables.cs @@ -44,7 +44,7 @@ public void TestBooleanVariable() Assert.IsTrue(ParameterCompiler.Evaluate(null, expression, out var result, ref error), error); if (result is bool boolResult) { - Assert.AreEqual(true, boolResult); + Assert.IsTrue(boolResult); } else { diff --git a/tests/XTMF2.UnitTests/TestModelSystem.cs b/tests/XTMF2.UnitTests/TestModelSystem.cs index ab70fc5..293aee7 100644 --- a/tests/XTMF2.UnitTests/TestModelSystem.cs +++ b/tests/XTMF2.UnitTests/TestModelSystem.cs @@ -60,7 +60,7 @@ 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 @@ -193,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); }); @@ -213,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); }); } @@ -233,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); }); } @@ -256,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); }); @@ -304,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); }); @@ -401,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 4d1563a..c7aa38b 100644 --- a/tests/XTMF2.UnitTests/TestProjects.cs +++ b/tests/XTMF2.UnitTests/TestProjects.cs @@ -117,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); } @@ -320,7 +320,7 @@ public void ImportProjectFileNoModelSystem() using (importedSession) { var modelSystems = importedSession.ModelSystems; - Assert.AreEqual(0, modelSystems.Count); + Assert.IsEmpty(modelSystems); } } finally @@ -358,7 +358,7 @@ public void ImportProjectFileSingleModelSystem() using (importedSession) { var modelSystems = importedSession.ModelSystems; - Assert.AreEqual(1, modelSystems.Count); + Assert.HasCount(1, modelSystems); } } finally @@ -400,7 +400,7 @@ public void ImportProjectFileMultipleModelSystem() using (importedSession) { var modelSystems = importedSession.ModelSystems; - Assert.AreEqual(numberOfModelSystems, modelSystems.Count); + Assert.HasCount(numberOfModelSystems, modelSystems); } } finally @@ -440,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."); } @@ -542,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!"); }); } @@ -555,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!"); }); } @@ -568,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!"); }); } @@ -583,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!"); }); } @@ -600,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!"); }); } @@ -616,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!"); }); } @@ -632,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!"); }); } @@ -649,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 ebfc7a3..e0b4b62 100644 --- a/tests/XTMF2.UnitTests/TestUsers.cs +++ b/tests/XTMF2.UnitTests/TestUsers.cs @@ -105,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)); @@ -144,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)); @@ -184,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); @@ -199,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)); @@ -232,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); @@ -249,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)); @@ -279,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 9369da3..5b1e733 100644 --- a/tests/XTMF2.UnitTests/XTMF2.UnitTests.csproj +++ b/tests/XTMF2.UnitTests/XTMF2.UnitTests.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 XTMF2.UnitTests @@ -21,9 +21,9 @@ - - - + + + 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