diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 284b7cd..6c1d729 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -6,6 +6,25 @@ on: - '**' jobs: + publish-nuget: + if: ${{ startsWith(github.ref, 'refs/tags/dotnet/') }} + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./dotnet + steps: + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 9.x + - name: Restore dependencies + run: dotnet restore + - name: Package C# library + run: dotnet pack Multiflag -c Release -o ./pkg + - name: Push to nuget.org + run: dotnet nuget push ./pkg/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json + publish-npm: if: ${{ startsWith(github.ref, 'refs/tags/node/') }} runs-on: ubuntu-latest diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 059a8c3..4594b65 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,9 +18,11 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.x + dotnet-version: 9.x - name: Restore dependencies run: dotnet restore + - name: Build solution + run: dotnet build /warnaserror - name: Run tests run: dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:CoverletOutput='../coverage.xml' - name: Report coverage diff --git a/.gitignore b/.gitignore index fdbeb9d..f4fed02 100644 --- a/.gitignore +++ b/.gitignore @@ -532,4 +532,6 @@ dist .yarn/unplugged .yarn/build-state.yml .yarn/install-state.gz -.pnp.* \ No newline at end of file +.pnp.* + +*.sublime-workspace \ No newline at end of file diff --git a/README.md b/README.md index 96fee98..f26a34e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Multiflag is a tiny language-agnostic library that makes manipulating bitflags ( especially if you have flags that depend on each other (for example, if you're managing permissions). - [Multiflag .NET](dotnet/README.md) [![Nuget Package](https://img.shields.io/nuget/v/Multiflag)](https://www.nuget.org/packages/Multiflag) -- [Multiflag JavaScript/TypeScript](node/README.md) [![npm](https://img.shields.io/npm/v/multiflag)](https://https://www.npmjs.com/package/multiflag) +- [Multiflag JavaScript/TypeScript](node/README.md) [![npm](https://img.shields.io/npm/v/multiflag)](https://www.npmjs.com/package/multiflag) ## What it does @@ -27,16 +27,13 @@ This covers most of the common needs, but you can subclass `FlagSet` to work wit ## Compatible flag types -| .NET | JS | .NET (v1) | JS (v1) | -|:---------------------------------------------------------:|:----------------------------------------:|:----------:|:------------:| -| `U8BitflagSet`
`EnumBitflagSet` backed by `byte` | `NumberBitflagSet` | `Flag8` | `NumberFlag` | -| `U16BitflagSet`
`EnumBitflagSet` backed by `ushort` | `NumberBitflagSet` | `Flag16` | `NumberFlag` | -| `U32BitflagSet`
`EnumBitflagSet` backed by `uint` | `NumberBitflagSet` | `Flag32` | `NumberFlag` | -| `U64BitflagSet`
`EnumBitflagSet` backed by `ulong` | `DynamicBitflagSet` | `Flag64` | ✗ | -| `DynamicBitflagSet` | `DynamicBitflagSet` | ✗ | ✗ | -| `Base64BitflagSet` | `Base64BitflagSet` | ✗ | ✗ | -| `CollectionFlagSet`
`ListFlagSet` | `CollectionFlagSet`
`ArrayFlagSet` | `FlagSet` | `ArrayFlag` | -| ✗ | `NumberBitflagSet` | `FlagEnum` | `NumberFlag` | +| .NET | JS | .NET (v1) | JS (v1) | +|:---------------------------------------------------------:|:----------------------------------------:|:-------------------------------------:|:------------:| +| `U32BitflagSet`
`EnumBitflagSet` backed by `uint` | `NumberBitflagSet` | `Flag8`
`Flag16`
`Flag32` | `NumberFlag` | +| `U64BitflagSet`
`EnumBitflagSet` backed by `ulong` | `DynamicBitflagSet` | `Flag64` | ✗ | +| `DynamicBitflagSet` | `DynamicBitflagSet` | ✗ | ✗ | +| `Base64BitflagSet` | `Base64BitflagSet` | ✗ | ✗ | +| `CollectionFlagSet`
`ListFlagSet` | `CollectionFlagSet`
`ArrayFlagSet` | `FlagSet` | `ArrayFlag` | ## Why it exists @@ -46,4 +43,4 @@ may come later. ## Licensing -Multiglag is available under the [MIT License](LICENSE). ⓒ 2023-2025 Louis DEVIE. +Multiflag is available under the [MIT License](LICENSE). ⓒ 2023-2025 Louis DEVIE. diff --git a/docs/Multiflag docs.sublime-project b/docs/Multiflag docs.sublime-project new file mode 100644 index 0000000..8788d4b --- /dev/null +++ b/docs/Multiflag docs.sublime-project @@ -0,0 +1,11 @@ +{ + "folders": + [ + { + "path": "source" + } + ], + "settings": { + "rulers": [80] + } +} diff --git a/docs/requirements.txt b/docs/requirements.txt index e69de29..ef5f826 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -0,0 +1,30 @@ +alabaster==1.0.0 +Babel==2.13.1 +beautifulsoup4==4.12.2 +certifi==2023.11.17 +charset-normalizer==3.3.2 +docutils==0.20.1 +furo==2024.8.6 +idna==3.6 +imagesize==1.4.1 +Jinja2==3.1.2 +lxml==4.9.3 +MarkupSafe==2.1.3 +packaging==23.2 +Pygments==2.17.2 +PyYAML==6.0.2 +requests==2.31.0 +snowballstemmer==2.2.0 +soupsieve==2.5 +Sphinx==8.1.3 +sphinx-basic-ng==1.0.0b2 +sphinx-tabs==3.4.7 +sphinxcontrib-applehelp==1.0.7 +sphinxcontrib-devhelp==2.0.0 +sphinxcontrib-htmlhelp==2.1.0 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-mermaid==1.0.0 +sphinxcontrib-qthelp==1.0.6 +sphinxcontrib-serializinghtml==1.1.9 +tomli==2.2.1 +urllib3==2.1.0 diff --git a/docs/source/_static/overrides.css b/docs/source/_static/overrides.css new file mode 100644 index 0000000..2511d13 --- /dev/null +++ b/docs/source/_static/overrides.css @@ -0,0 +1,3 @@ +.sphinx-tabs-tab { + padding: 0.5rem 1rem !important; +} diff --git a/docs/source/common/flags.rst b/docs/source/common/flags.rst new file mode 100644 index 0000000..5f603b2 --- /dev/null +++ b/docs/source/common/flags.rst @@ -0,0 +1,128 @@ +Flags +===== + +A ``Flag`` represents some element of a set. + +.. tabs:: + .. code-tab:: cs + + public class Flag + { + public virtual bool IsAbstract { get; } + + public virtual TSet AddTo(TSet flags); + + public static TSet operator +(TSet value, Flag flag); + + public virtual TSet RemoveFrom(TSet flags); + + public static TSet operator -(TSet value, Flag flag); + + public virtual bool IsIn(TSet flags); + } + + .. code-tab:: ts + + export class Flag { + public readonly isAbstract: boolean + + public addTo(flags: T): T + + public removeFrom(flags: T): T + + public isIn(flags: T): boolean + } + +Add to set +---------- + +Adds a flag and its parents to a set if they are not already present. This +operation will return a new set of flags, or the same set if it hasn't been +modified. The input set will never be modified in-place. + +.. tabs:: + .. code-tab:: cs + + var newSet = flag.AddTo(mySet); + var newSet = mySet + flag; + mySet += flag; + + .. code-tab:: ts + + const newSet = flag.addTo(mySet); + +.. versionchanged:: 2.0 + this method won't modify the set in-place anymore. +.. versionadded:: 1.0 + + +Remove from set +--------------- + +Removes a flag and any of its children that appear in a set. This operation will +return a new set of flags, or the same set if it hasn't been modified. The input +set will never be modified in-place. + +.. tabs:: + .. code-tab:: cs + + var newSet = flag.RemoveFrom(mySet); + var newSet = mySet - flag; + mySet -= flag; + + .. code-tab:: ts + + const newSet = flag.removeFrom(mySet); + +.. versionchanged:: 2.0 + this method won't modify the set in-place anymore. +.. versionadded:: 1.0 + + +Is in set +--------- + +Tests if a flag and all its parents are present in a set. + +.. tabs:: + .. code-tab:: cs + + if(flag.IsIn(mySet)) { + ... + } + + .. code-tab:: ts + + if(flag.isIn(mySet)) { + ... + } + +.. versionchanged:: 2.0 + this method is now called ``IsIn`` instead of ``In``. +.. versionadded:: 1.0 + + +Is abstract +----------- + +If this property is ``true``, it indicates that the flag has no value on its +own. + +.. tabs:: + .. code-tab:: cs + + if(flag.IsAbstract) { + ... + } + + .. code-tab:: ts + + if(flag.isAbstract) { + ... + } + +.. versionchanged:: 2.0 + a value of ``true`` is equivalent to either ``IsAbstract`` or ``IsNothing`` + being ``true`` in v1, and a value of ``false`` is equivalent to + ``IsConcrete`` being ``true``. +.. versionadded:: 1.0 \ No newline at end of file diff --git a/docs/source/common/flagsets.rst b/docs/source/common/flagsets.rst new file mode 100644 index 0000000..90b822f --- /dev/null +++ b/docs/source/common/flagsets.rst @@ -0,0 +1,11 @@ +Flagsets +======== + +A ``FlagSet`` is a group of flags that can be combined with each other. + +Declaring a flagset +------------------- + +Anonymous flagset +***************** + diff --git a/docs/source/common/index.rst b/docs/source/common/index.rst new file mode 100644 index 0000000..b4fa552 --- /dev/null +++ b/docs/source/common/index.rst @@ -0,0 +1,9 @@ +Common APIs +=========== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + flagsets.rst + flags.rst diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst new file mode 100644 index 0000000..225a3c1 --- /dev/null +++ b/docs/source/concepts.rst @@ -0,0 +1,16 @@ +Concepts +======== + +Flags hierarchy +--------------- + +.. mermaid:: + :align: center + + flowchart TD + B(("B")) --> A(("A")) + C(("C")) --> A + D(("D")) --> B + E(("E")) --> B + F(("F")) --> C + class E,B,A mmd-node-added \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 1453ccd..b9734aa 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -18,11 +18,11 @@ # -- Project information ----------------------------------------------------- project = "Multiflag" -copyright = "2023, Louis DEVIE" +copyright = "2023-2025, Louis DEVIE" author = "Louis DEVIE" # The full version, including alpha/beta/rc tags -release = "1.0" +release = "2.0" # -- General configuration --------------------------------------------------- @@ -30,7 +30,7 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [] +extensions = ["sphinx_tabs.tabs", "sphinxcontrib.mermaid"] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -52,3 +52,12 @@ # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] + +html_css_files = ["overrides.css"] + + +# -- reST configuration ------------------------------------------------------ + +rst_prolog = """ +.. role:: badge +""" diff --git a/docs/source/dotnet/index.rst b/docs/source/dotnet/index.rst new file mode 100644 index 0000000..08bebf9 --- /dev/null +++ b/docs/source/dotnet/index.rst @@ -0,0 +1,7 @@ +C# API +====== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + diff --git a/docs/source/index.rst b/docs/source/index.rst index 4118da2..81342a4 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,20 +1,11 @@ -.. Multiflag documentation master file, created by - sphinx-quickstart on Sat Dec 9 14:29:33 2023. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to Multiflag's documentation! -===================================== +Multiflag docs +============== .. toctree:: :maxdepth: 2 :caption: Contents: - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` + concepts + common/index.rst + dotnet/index.rst + node/index.rst diff --git a/docs/source/node/index.rst b/docs/source/node/index.rst new file mode 100644 index 0000000..e7be4c7 --- /dev/null +++ b/docs/source/node/index.rst @@ -0,0 +1,7 @@ +JavaScript API +============== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + diff --git a/dotnet/.idea/.idea.Multiflag/.idea/.gitignore b/dotnet/.idea/.idea.Multiflag/.idea/.gitignore new file mode 100644 index 0000000..7df85a9 --- /dev/null +++ b/dotnet/.idea/.idea.Multiflag/.idea/.gitignore @@ -0,0 +1,16 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/modules.xml +/projectSettingsUpdater.xml +/contentModel.xml +/.idea.Multiflag.iml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +.name +discord.xml +indexLayout.xml \ No newline at end of file diff --git a/dotnet/.idea/.idea.Multiflag/.idea/codeStyles/codeStyleConfig.xml b/dotnet/.idea/.idea.Multiflag/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/dotnet/.idea/.idea.Multiflag/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/dotnet/.idea/.idea.Multiflag/.idea/vcs.xml b/dotnet/.idea/.idea.Multiflag/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/dotnet/.idea/.idea.Multiflag/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/dotnet/Multiflag.sln b/dotnet/Multiflag.sln index 31bc844..7d2ab1b 100644 --- a/dotnet/Multiflag.sln +++ b/dotnet/Multiflag.sln @@ -10,6 +10,8 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Éléments de solution", "Éléments de solution", "{A2326173-037B-4F50-B202-D385541624BE}" ProjectSection(SolutionItems) = preProject README.md = README.md + ..\.github\workflows\tests.yml = ..\.github\workflows\tests.yml + ..\.github\workflows\publish.yml = ..\.github\workflows\publish.yml EndProjectSection EndProject Global diff --git a/dotnet/Multiflag.sln.DotSettings b/dotnet/Multiflag.sln.DotSettings new file mode 100644 index 0000000..fb4ecac --- /dev/null +++ b/dotnet/Multiflag.sln.DotSettings @@ -0,0 +1,3 @@ + + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="True" Prefix="" Suffix="" Style="aaBb" /></Policy> \ No newline at end of file diff --git a/dotnet/Multiflag/Base64BitflagSet.cs b/dotnet/Multiflag/Base64BitflagSet.cs new file mode 100644 index 0000000..f5d5c29 --- /dev/null +++ b/dotnet/Multiflag/Base64BitflagSet.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using Multiflag.Base64Format; +using Multiflag.Enumerators; + +namespace Multiflag +{ + /// + /// Provides flags that are stored in strings using a little-endian base 64 + /// representation. + ///
+ /// This format is compact, easily serializable and allows for an unlimited + /// number of flags, but is specific to Multiflag. + /// Use instead if you need the data to be + /// easily understandable by other systems. + ///
+ public class Base64BitflagSet : FlagSet + { + /// + protected override sealed string WrapValue(int value) + { + if (value < 1) + { + throw new ArgumentOutOfRangeException(nameof(value), "Indices should be greater than or equal to 1."); + } + + return Base64Codec.EncodeSingleFlag(value); + } + + /// + public override sealed string Empty() + { + return Base64Codec.Zero; + } + + /// + public override sealed string Union(string first, string second) + { + return Base64Codec.BitwiseOr(first, second); + } + + /// + public override sealed string Intersection(string first, string second) + { + return Base64Codec.BitwiseAnd(first, second); + } + + /// + public override sealed string Difference(string first, string second) + { + return Base64Codec.BitwiseAndNot(first, second); + } + + /// + public override sealed bool IsSupersetOf(string first, string second) + { + return Base64Codec.BitwiseAndEquals(first, second); + } + + /// + public override sealed IEnumerable Iterate(string flags) + { + return new Base64FlagEnumerator(flags); + } + } +} \ No newline at end of file diff --git a/dotnet/Multiflag/Base64Format/Base64Codec.cs b/dotnet/Multiflag/Base64Format/Base64Codec.cs new file mode 100644 index 0000000..ddb44e8 --- /dev/null +++ b/dotnet/Multiflag/Base64Format/Base64Codec.cs @@ -0,0 +1,214 @@ +using System; +using System.Text; + +namespace Multiflag.Base64Format +{ + internal static class Base64Codec + { + public const char ZERO = 'A'; + public const char TWENTY_SIX = 'a'; + public const char FIFTY_TWO = '0'; + public const char SIXTY_TWO = '-'; + public const char SIXTY_THREE = '_'; + + private static readonly string zero = ZERO.ToString(); + + public static string Zero => zero; + + public static char EncodeByte(int value) + { + if (value < 26) + { + return (char)(ZERO + value); + } + else if (value < 52) + { + return (char)(TWENTY_SIX + value - 26); + } + else if (value < 62) + { + return (char)(FIFTY_TWO + value - 52); + } + else + { + return value == 62 ? SIXTY_TWO : SIXTY_THREE; + } + } + + public static int DecodeByte(char encodedValue) + { + if (encodedValue == SIXTY_THREE) + { + return 63; + } + else if (encodedValue == SIXTY_TWO) + { + return 62; + } + else if (encodedValue >= TWENTY_SIX) + { + return encodedValue - TWENTY_SIX + 26; + } + else if (encodedValue >= ZERO) + { + return encodedValue - ZERO; + } + else + { + return encodedValue - FIFTY_TWO + 52; + } + } + + public static string EncodeSingleFlag(int flagIndex) + { + int indexFromZero = flagIndex - 1; + int bigEnd = indexFromZero % 6; + int leadingBytes = indexFromZero / 6; + return new string(ZERO, leadingBytes) + EncodeByte((byte)(1 << bigEnd)); + } + + public static bool DecodesToZero(string encodedValue) + { + var result = true; + for (var i = 0; i < encodedValue.Length && result; i++) + { + result = encodedValue[i] == ZERO; + } + + return result; + } + + public static bool DecodesToSingleFlag(string encodedValue) + { + var powersOfTwo = 0; + foreach (char t in encodedValue) + { + int value = DecodeByte(t); + if (value != 0 && (value & value - 1) == 0) + { + powersOfTwo++; + } + } + + return powersOfTwo == 1; + } + + public static string BitwiseOr(string a, string b) + { + var result = new StringBuilder(); + + string shorter, longer; + if (a.Length < b.Length) + { + shorter = a; + longer = b; + } + else + { + shorter = b; + longer = a; + } + + var i = 0; + // OR the bytes one by one + for (; i < shorter.Length; i++) + { + int value = DecodeByte(shorter[i]) | DecodeByte(longer[i]); + result.Append(EncodeByte(value)); + } + + // if one string is longer than the other, append the remaining bytes (x | 0 = x) + for (; i < longer.Length; i++) + { + result.Append(longer[i]); + } + + // make sure there is always one digit in the string + // empty strings are considered equal to zero, but we always try to normalise the output + if (i < 1) + { + result.Append(ZERO); + } + + return result.ToString(); + } + + public static string BitwiseAnd(string first, string second) + { + var result = new StringBuilder(); + + int shorterLength = Math.Min(first.Length, second.Length); + var i = 0; + // AND the bytes one by one + for (; i < shorterLength; i++) + { + int value = DecodeByte(first[i]) & DecodeByte(second[i]); + result.Append(EncodeByte(value)); + } + + // if one string is longer then the other, don't add anything (x & 0 = 0) + // but make sure there is always one digit in the string + // empty strings are considered equal to zero, but we always try to normalise the output + if (i < 1) + { + result.Append(ZERO); + } + + return result.ToString(); + } + + public static string BitwiseAndNot(string first, string second) + { + var result = new StringBuilder(); + + int shorterLength = Math.Min(first.Length, second.Length); + var i = 0; + // AND the bytes one by one + for (; i < shorterLength; i++) + { + int value = DecodeByte(first[i]) & ~DecodeByte(second[i]); + result.Append(EncodeByte(value)); + } + + // if the first string is longer than the other, append its remaining bytes (x & ~0 = x) + // if the second string is longer, don't add anything (0 & ~y = 0) + for (; i < first.Length; i++) + { + result.Append(first[i]); + } + + // make sure there is always one digit in the string + // empty strings are considered equal to zero, but we always try to normalise the output + if (i < 1) + { + result.Append(ZERO); + } + + return result.ToString(); + } + + public static bool BitwiseAndEquals(string first, string second) + { + var result = true; + + int shorterLength = Math.Min(first.Length, second.Length); + var i = 0; + // AND the bytes one by one and check + // if one is false we don't need to check further + for (; i < shorterLength && result; i++) + { + int secondValue = DecodeByte(second[i]); + result = (DecodeByte(first[i]) & secondValue) == secondValue; + } + + // if there are more characters in the second string, they must all be zeros + // (0 & x is only equal to x when x is also 0) + for (; i < second.Length && result; i++) + { + result = second[i] == ZERO; + } + + return result; + } + } +} \ No newline at end of file diff --git a/dotnet/Multiflag/Bitflags/BigintBitManipulator.cs b/dotnet/Multiflag/Bitflags/BigintBitManipulator.cs new file mode 100644 index 0000000..7b5251b --- /dev/null +++ b/dotnet/Multiflag/Bitflags/BigintBitManipulator.cs @@ -0,0 +1,64 @@ +using System.Numerics; + +namespace Multiflag.Bitflags +{ + internal class BigintBitManipulator : BitManipulator + { + private static BigintBitManipulator? current; + + private BigintBitManipulator() + { + } + + public static BigintBitManipulator Current => current ??= new BigintBitManipulator(); + + public override BigInteger Zero => BigInteger.Zero; + + public override BigInteger One => BigInteger.One; + + public override bool IsZero(BigInteger value) + { + return value.IsZero; + } + + public override bool IsEven(BigInteger value) + { + return value.IsEven; + } + + protected override bool IsPowerOfTwo(BigInteger value) + { + return value.IsPowerOfTwo; + } + + public override BigInteger BitwiseOr(BigInteger first, BigInteger second) + { + return first | second; + } + + public override BigInteger BitwiseAnd(BigInteger first, BigInteger second) + { + return first & second; + } + + public override BigInteger BitwiseAndNot(BigInteger first, BigInteger second) + { + return first & ~second; + } + + public override bool BitwiseAndEquals(BigInteger first, BigInteger second) + { + return (first & second) == second; + } + + public override BigInteger ShiftLeft(BigInteger value) + { + return value << 1; + } + + public override BigInteger ShiftRight(BigInteger value) + { + return value >> 1; + } + } +} \ No newline at end of file diff --git a/dotnet/Multiflag/Bitflags/BitManipulator.cs b/dotnet/Multiflag/Bitflags/BitManipulator.cs new file mode 100644 index 0000000..9370245 --- /dev/null +++ b/dotnet/Multiflag/Bitflags/BitManipulator.cs @@ -0,0 +1,58 @@ +namespace Multiflag.Bitflags +{ + internal abstract class BitManipulator : IBitManipulator + where T : notnull + { + public abstract T Zero { get; } + + object IBitManipulator.Zero => this.Zero; + + public abstract T One { get; } + + object IBitManipulator.One => this.One; + + public void CheckPowerOfTwo(T value) + { + if (!this.IsPowerOfTwo(value)) + { + throw new InvalidBitflagValueException(); + } + } + + public void CheckPowerOfTwo(object value) => this.CheckPowerOfTwo((T)value); + + public abstract bool IsZero(T value); + + public bool IsZero(object value) => this.IsZero((T)value); + + public abstract bool IsEven(T value); + + public bool IsEven(object value) => this.IsEven((T)value); + + protected abstract bool IsPowerOfTwo(T value); + + public abstract T ShiftLeft(T value); + + public object ShiftLeft(object value) => this.ShiftLeft((T) value); + + public abstract T ShiftRight(T value); + + public object ShiftRight(object value) => this.ShiftRight((T) value); + + public abstract T BitwiseOr(T first, T second); + + public object BitwiseOr(object first, object second) => this.BitwiseOr((T)first, (T)second); + + public abstract T BitwiseAnd(T first, T second); + + public object BitwiseAnd(object first, object second) => this.BitwiseAnd((T)first, (T)second); + + public abstract T BitwiseAndNot(T first, T second); + + public object BitwiseAndNot(object first, object second) => this.BitwiseAndNot((T)first, (T)second); + + public abstract bool BitwiseAndEquals(T first, T second); + + public bool BitwiseAndEquals(object first, object second) => this.BitwiseAndEquals((T)first, (T)second); + } +} \ No newline at end of file diff --git a/dotnet/Multiflag/Bitflags/IBitManipulator.cs b/dotnet/Multiflag/Bitflags/IBitManipulator.cs new file mode 100644 index 0000000..1308f70 --- /dev/null +++ b/dotnet/Multiflag/Bitflags/IBitManipulator.cs @@ -0,0 +1,27 @@ +namespace Multiflag.Bitflags +{ + internal interface IBitManipulator + { + public object Zero { get; } + + public object One { get; } + + public void CheckPowerOfTwo(object value); + + public bool IsZero(object value); + + public bool IsEven(object value); + + public object ShiftLeft(object value); + + public object ShiftRight(object value); + + public object BitwiseOr(object first, object second); + + public object BitwiseAnd(object first, object second); + + public object BitwiseAndNot(object first, object second); + + public bool BitwiseAndEquals(object first, object second); + } +} \ No newline at end of file diff --git a/dotnet/Multiflag/Bitflags/U32BitManipulator.cs b/dotnet/Multiflag/Bitflags/U32BitManipulator.cs new file mode 100644 index 0000000..edf92dc --- /dev/null +++ b/dotnet/Multiflag/Bitflags/U32BitManipulator.cs @@ -0,0 +1,56 @@ +namespace Multiflag.Bitflags +{ + internal class U32BitManipulator : BitManipulator + { + private static U32BitManipulator? current; + + private U32BitManipulator() + { + } + + public static U32BitManipulator Current => current ??= new U32BitManipulator(); + + public override uint Zero => 0; + + public override uint One => 1; + + public override bool IsZero(uint value) => value == 0; + + public override bool IsEven(uint value) => (value & 1) == 0; + + protected override bool IsPowerOfTwo(uint value) + { + return value != 0 && (value & value - 1) == 0; + } + + public override uint ShiftLeft(uint value) + { + return value << 1; + } + + public override uint ShiftRight(uint value) + { + return value >> 1; + } + + public override uint BitwiseOr(uint first, uint second) + { + return first | second; + } + + public override uint BitwiseAnd(uint first, uint second) + { + return first & second; + } + + public override uint BitwiseAndNot(uint first, uint second) + { + return first & ~second; + } + + public override bool BitwiseAndEquals(uint first, uint second) + { + return (first & second) == second; + } + } +} \ No newline at end of file diff --git a/dotnet/Multiflag/Bitflags/U64BitManipulator.cs b/dotnet/Multiflag/Bitflags/U64BitManipulator.cs new file mode 100644 index 0000000..949a3aa --- /dev/null +++ b/dotnet/Multiflag/Bitflags/U64BitManipulator.cs @@ -0,0 +1,56 @@ +namespace Multiflag.Bitflags +{ + internal class U64BitManipulator : BitManipulator + { + private static U64BitManipulator? current; + + private U64BitManipulator() + { + } + + public static U64BitManipulator Current => current ??= new U64BitManipulator(); + + public override ulong Zero => 0; + + public override ulong One => 1; + + public override bool IsZero(ulong value) => value == 0; + + public override bool IsEven(ulong value) => (value & 1) == 0; + + protected override bool IsPowerOfTwo(ulong value) + { + return value != 0 && (value & value - 1) == 0; + } + + public override ulong ShiftLeft(ulong value) + { + return value << 1; + } + + public override ulong ShiftRight(ulong value) + { + return value >> 1; + } + + public override ulong BitwiseOr(ulong first, ulong second) + { + return first | second; + } + + public override ulong BitwiseAnd(ulong first, ulong second) + { + return first & second; + } + + public override ulong BitwiseAndNot(ulong first, ulong second) + { + return first & ~second; + } + + public override bool BitwiseAndEquals(ulong first, ulong second) + { + return (first & second) == second; + } + } +} \ No newline at end of file diff --git a/dotnet/Multiflag/BuiltInValueTypes/ByteFlagValue.cs b/dotnet/Multiflag/BuiltInValueTypes/ByteFlagValue.cs deleted file mode 100644 index 5ba99ce..0000000 --- a/dotnet/Multiflag/BuiltInValueTypes/ByteFlagValue.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Multiflag.BuiltInValueTypes -{ - /// - /// Built-in implementation of for . - /// - public class ByteFlagValue : IFlagValue - { - public byte Substraction(byte first, byte second) => (byte)(first & ~second); - - public bool Includes(byte first, byte second) => (first & second) == second; - - public byte Union(byte first, byte second) => (byte)(first | second); - - public byte Nothing() => 0; - - public bool Equivalent(byte first, byte second) => first == second; - } -} \ No newline at end of file diff --git a/dotnet/Multiflag/BuiltInValueTypes/EnumFlagValue.cs b/dotnet/Multiflag/BuiltInValueTypes/EnumFlagValue.cs deleted file mode 100644 index e4ba077..0000000 --- a/dotnet/Multiflag/BuiltInValueTypes/EnumFlagValue.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace Multiflag.BuiltInValueTypes -{ - public class InvalidEnumTypeException : Exception - { - public InvalidEnumTypeException(string message) : base(message) { } - } - - /// - /// Built-in implementation of for s backed by . - /// - public class EnumFlagValue : IFlagValue where TEnum : Enum - { - public EnumFlagValue() - { - if (!typeof(int).IsAssignableFrom(Enum.GetUnderlyingType(typeof(TEnum)))) - throw new InvalidEnumTypeException("FlagEnums are only compatible with int-backed enums."); - } - - private int EnumToInt(TEnum value) - { - return (int)(object)value; - } - - private TEnum IntToEnum(int value) - { - return (TEnum)(object)value; - } - - public TEnum Substraction(TEnum first, TEnum second) - { - int intFirst = EnumToInt(first); - int intSecond = EnumToInt(second); - return IntToEnum(intFirst & ~intSecond); - } - - public bool Includes(TEnum first, TEnum second) - { - int intFirst = EnumToInt(first); - int intSecond = EnumToInt(second); - return (intFirst & intSecond) == intSecond; - } - - public TEnum Union(TEnum first, TEnum second) - { - int intFirst = EnumToInt(first); - int intSecond = EnumToInt(second); - return IntToEnum(intFirst | intSecond); - } - - public TEnum Nothing() => IntToEnum(0); - - public bool Equivalent(TEnum first, TEnum second) => first.Equals(second); - } -} \ No newline at end of file diff --git a/dotnet/Multiflag/BuiltInValueTypes/SetFlagValue.cs b/dotnet/Multiflag/BuiltInValueTypes/SetFlagValue.cs deleted file mode 100644 index 5e20dd5..0000000 --- a/dotnet/Multiflag/BuiltInValueTypes/SetFlagValue.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace Multiflag.BuiltInValueTypes -{ - /// - /// Built-in implementation of for . - /// - /// - /// The set is modified in-place. If you do not want this behavior, make a - /// copy of your set before using flag operation on it. - /// - public class SetFlagValue : IFlagValue> - { - public HashSet Substraction(HashSet first, HashSet second) - { - first.ExceptWith(second); - return first; - } - - public bool Includes(HashSet first, HashSet second) - { - return first.IsSupersetOf(second); - } - - public HashSet Nothing() - { - return new HashSet(); - } - - public HashSet Union(HashSet first, HashSet second) - { - first.UnionWith(second); - return first; - } - - public bool Equivalent(HashSet first, HashSet second) => first.SetEquals(second); - } -} diff --git a/dotnet/Multiflag/BuiltInValueTypes/UIntFlagValue.cs b/dotnet/Multiflag/BuiltInValueTypes/UIntFlagValue.cs deleted file mode 100644 index b5ca446..0000000 --- a/dotnet/Multiflag/BuiltInValueTypes/UIntFlagValue.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Multiflag.BuiltInValueTypes -{ - /// - /// Built-in implementation of for . - /// - public class UIntFlagValue : IFlagValue - { - public uint Substraction(uint first, uint second) => first & ~second; - - public bool Includes(uint first, uint second) => (first & second) == second; - - public uint Union(uint first, uint second) => first | second; - - public uint Nothing() => 0; - - public bool Equivalent(uint first, uint second) => first == second; - } -} \ No newline at end of file diff --git a/dotnet/Multiflag/BuiltInValueTypes/ULongFlagValue.cs b/dotnet/Multiflag/BuiltInValueTypes/ULongFlagValue.cs deleted file mode 100644 index cc610ee..0000000 --- a/dotnet/Multiflag/BuiltInValueTypes/ULongFlagValue.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Multiflag.BuiltInValueTypes -{ - /// - /// Built-in implementation of for . - /// - public class ULongFlagValue : IFlagValue - { - public ulong Substraction(ulong first, ulong second) => first & ~second; - - public bool Includes(ulong first, ulong second) => (first & second) == second; - - public ulong Union(ulong first, ulong second) => first | second; - - public ulong Nothing() => 0; - - public bool Equivalent(ulong first, ulong second) => first == second; - } -} \ No newline at end of file diff --git a/dotnet/Multiflag/BuiltInValueTypes/UShortFlagValue.cs b/dotnet/Multiflag/BuiltInValueTypes/UShortFlagValue.cs deleted file mode 100644 index ea1ae8f..0000000 --- a/dotnet/Multiflag/BuiltInValueTypes/UShortFlagValue.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Multiflag.BuiltInValueTypes -{ - /// - /// Built-in implementation of for . - /// - public class UShortFlagValue : IFlagValue - { - public ushort Substraction(ushort first, ushort second) => (ushort)(first & ~second); - - public bool Includes(ushort first, ushort second) => (first & second) == second; - - public ushort Union(ushort first, ushort second) => (ushort)(first | second); - - public ushort Nothing() => 0; - - public bool Equivalent(ushort first, ushort second) => first == second; - } -} \ No newline at end of file diff --git a/dotnet/Multiflag/CollectionFlagSet.cs b/dotnet/Multiflag/CollectionFlagSet.cs new file mode 100644 index 0000000..e27ef3d --- /dev/null +++ b/dotnet/Multiflag/CollectionFlagSet.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Multiflag +{ + /// + /// Provides flags that work on HashSets. The flags can have values of any type and there is no limit to the number + /// of different flags, and sets can easily be serialized (as a JSON array for example). + /// + public class CollectionFlagSet : FlagSet> + where T : notnull + { + /// + protected override sealed IImmutableSet WrapValue(T value) + { + return ImmutableHashSet.Create(value); + } + + /// + public override sealed IImmutableSet Empty() + { + return ImmutableHashSet.Empty; + } + + /// + public override sealed IImmutableSet Union(IImmutableSet first, IImmutableSet second) + { + return first.Union(second); + } + + /// + public override sealed IImmutableSet Intersection(IImmutableSet first, IImmutableSet second) + { + return first.Intersect(second); + } + + /// + public override sealed IImmutableSet Difference(IImmutableSet first, IImmutableSet second) + { + return first.Except(second); + } + + /// + public override sealed bool IsSupersetOf(IImmutableSet first, IImmutableSet second) + { + return first.IsSupersetOf(second); + } + + /// + public override IEnumerable Iterate(IImmutableSet flags) + { + return flags; + } + } +} \ No newline at end of file diff --git a/dotnet/Multiflag/DynamicBitflagSet.cs b/dotnet/Multiflag/DynamicBitflagSet.cs new file mode 100644 index 0000000..320a73c --- /dev/null +++ b/dotnet/Multiflag/DynamicBitflagSet.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Numerics; +using Multiflag.Bitflags; +using Multiflag.Enumerators; + +namespace Multiflag +{ + /// + /// Provides bitflags based on dynamic integers (thus allowing any number of flags). + /// + public class DynamicBitflagSet : FlagSet + { + /// + protected override sealed BigInteger WrapValue(BigInteger value) + { + BigintBitManipulator.Current.CheckPowerOfTwo(value); + return value; + } + + /// + public override sealed BigInteger Empty() + { + return BigintBitManipulator.Current.Zero; + } + + /// + public override sealed BigInteger Union(BigInteger first, BigInteger second) + { + return BigintBitManipulator.Current.BitwiseOr(first, second); + } + + /// + public override sealed BigInteger Intersection(BigInteger first, BigInteger second) + { + return BigintBitManipulator.Current.BitwiseAnd(first, second); + } + + /// + public override sealed BigInteger Difference(BigInteger first, BigInteger second) + { + return BigintBitManipulator.Current.BitwiseAndNot(first, second); + } + + /// + public override sealed bool IsSupersetOf(BigInteger first, BigInteger second) + { + return BigintBitManipulator.Current.BitwiseAndEquals(first, second); + } + + /// + public override IEnumerable Iterate(BigInteger flags) + { + return new NumericFlagEnumerator( + BigintBitManipulator.Current, + flags + ); + } + } +} \ No newline at end of file diff --git a/dotnet/Multiflag/EnumBitflagSet.cs b/dotnet/Multiflag/EnumBitflagSet.cs new file mode 100644 index 0000000..1629136 --- /dev/null +++ b/dotnet/Multiflag/EnumBitflagSet.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using Multiflag.Bitflags; +using Multiflag.Enumerators; + +namespace Multiflag +{ + /// + /// Provides bitflags based on dynamic integers (thus allowing any number of flags). + /// + public class EnumBitflagSet : FlagSet + where T : Enum + { + private readonly IBitManipulator bitManipulator; + private readonly Type enumType; + private readonly Type underlyingType; + + /// + /// Creates a new empty flag set for a specific enum type. + /// + /// + /// If is not backed by one of , + /// , or . + /// + public EnumBitflagSet() + { + this.enumType = typeof(T); + this.underlyingType = this.enumType.GetEnumUnderlyingType(); + this.bitManipulator = FindBitManipulator(this.underlyingType); + } + + private static IBitManipulator FindBitManipulator(Type underlyingType) + { + if (underlyingType == typeof(uint)) + { + return U32BitManipulator.Current; + } + else if (underlyingType == typeof(ulong)) + { + return U64BitManipulator.Current; + } + else if (underlyingType == typeof(long)) + { + throw new UnsupportedEnumTypeException(underlyingType, "ulong"); + } + else + { + throw new UnsupportedEnumTypeException(underlyingType, "uint"); + } + } + + private object EnumToInt(object value) + { + return Convert.ChangeType(value, this.underlyingType); + } + + private T IntToEnum(object value) + { + return (T)Enum.ToObject(this.enumType, value); + } + + /// + protected override sealed T WrapValue(T value) + { + this.bitManipulator.CheckPowerOfTwo(this.EnumToInt(value)); + + if (!Enum.IsDefined(this.enumType, value)) + { + throw new UndefinedEnumValueException(value); + } + + return value; + } + + /// + public override sealed T Empty() + { + return this.IntToEnum(this.bitManipulator.Zero); + } + + /// + public override sealed T Union(T first, T second) + { + return this.IntToEnum( + this.bitManipulator.BitwiseOr( + this.EnumToInt(first), + this.EnumToInt(second) + ) + ); + } + + /// + public override sealed T Intersection(T first, T second) + { + return this.IntToEnum( + this.bitManipulator.BitwiseAnd( + this.EnumToInt(first), + this.EnumToInt(second) + ) + ); + } + + /// + public override sealed T Difference(T first, T second) + { + return this.IntToEnum( + this.bitManipulator.BitwiseAndNot( + this.EnumToInt(first), + this.EnumToInt(second) + ) + ); + } + + /// + public override sealed bool IsSupersetOf(T first, T second) + { + return this.bitManipulator.BitwiseAndEquals( + this.EnumToInt(first), + this.EnumToInt(second) + ); + } + + /// + public override IEnumerable Iterate(T flags) + { + return new EnumFlagEnumerator( + this.bitManipulator, + flags + ); + } + } +} \ No newline at end of file diff --git a/dotnet/Multiflag/Enumerators/Base64FlagEnumerator.cs b/dotnet/Multiflag/Enumerators/Base64FlagEnumerator.cs new file mode 100644 index 0000000..b4ad546 --- /dev/null +++ b/dotnet/Multiflag/Enumerators/Base64FlagEnumerator.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Multiflag.Base64Format; + +namespace Multiflag.Enumerators +{ + internal class Base64FlagEnumerator : IEnumerable, IEnumerator + { + private readonly string value; + private int currentByte; + private int currentBit; + + public Base64FlagEnumerator(string value) + { + this.value = value; + this.currentByte = 0; + this.currentBit = 0; + } + + public IEnumerator GetEnumerator() + { + return new Base64FlagEnumerator(this.value); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + public void Dispose() + { + } + + private bool MoveNextByte() { + // next multiple of 6 + var index = (int)Math.Ceiling(this.currentBit / 6.0); + + // skip bytes equal to zero + while ( + index < this.value.Length + && this.value[index] == Base64Codec.ZERO) + { + index++; + } + + if (index < this.value.Length) + { + // found a non-zero byte + this.currentByte = Base64Codec.DecodeByte(this.value[index]); + this.currentBit = index * 6; + return true; + } + else + { + // reached the end of the string + return false; + } + } + + public bool MoveNext() + { + if (this.currentByte == 0) + { + if (!this.MoveNextByte()) + { + return false; + } + } + + while ((this.currentByte & 1) == 0) + { + this.currentByte >>= 1; + this.currentBit += 1; + } + + this.currentByte >>= 1; + this.currentBit += 1; + + return true; + } + + public void Reset() + { + this.currentByte = 0; + this.currentBit = 0; + } + + public int Current => this.currentBit; + + object IEnumerator.Current => this.Current; + } +} \ No newline at end of file diff --git a/dotnet/Multiflag/Enumerators/EnumFlagEnumerator.cs b/dotnet/Multiflag/Enumerators/EnumFlagEnumerator.cs new file mode 100644 index 0000000..dd2ad81 --- /dev/null +++ b/dotnet/Multiflag/Enumerators/EnumFlagEnumerator.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Multiflag.Bitflags; + +namespace Multiflag.Enumerators +{ + internal class EnumFlagEnumerator : IEnumerable, IEnumerator + { + private readonly object value; + private readonly IBitManipulator bitManipulator; + private object remaining; + private object current; + + public EnumFlagEnumerator(IBitManipulator bitManipulator, object value) + { + this.value = value; + this.bitManipulator = bitManipulator; + this.remaining = value; + this.current = this.bitManipulator.One; + } + + public IEnumerator GetEnumerator() + { + return new EnumFlagEnumerator(this.bitManipulator, this.value); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + public bool MoveNext() + { + if (this.bitManipulator.IsZero(this.remaining)) + { + return false; + } + + // move to the next bit + while (this.bitManipulator.IsEven(this.remaining)) + { + this.remaining = this.bitManipulator.ShiftRight(this.remaining); + this.current = this.bitManipulator.ShiftLeft(this.current); + } + + // discard this bit + this.remaining = this.bitManipulator.BitwiseAndNot( + this.remaining, + this.bitManipulator.One + ); + + return true; + } + + public void Reset() + { + this.remaining = this.value; + this.current = this.bitManipulator.One; + } + + T IEnumerator.Current => (T)this.Current; + + public object Current => Enum.ToObject(typeof(T), this.current); + + public void Dispose() + { + } + } +} \ No newline at end of file diff --git a/dotnet/Multiflag/Enumerators/NumericFlagEnumerator.cs b/dotnet/Multiflag/Enumerators/NumericFlagEnumerator.cs new file mode 100644 index 0000000..074e955 --- /dev/null +++ b/dotnet/Multiflag/Enumerators/NumericFlagEnumerator.cs @@ -0,0 +1,70 @@ +using System.Collections; +using System.Collections.Generic; +using Multiflag.Bitflags; + +namespace Multiflag.Enumerators +{ + internal class NumericFlagEnumerator : IEnumerable, IEnumerator + where T : notnull + { + private readonly T value; + private readonly BitManipulator bitManipulator; + private T remaining; + private T current; + + public NumericFlagEnumerator(BitManipulator bitManipulator, T value) + { + this.value = value; + this.bitManipulator = bitManipulator; + this.remaining = value; + this.current = this.bitManipulator.One; + } + + public IEnumerator GetEnumerator() + { + return new NumericFlagEnumerator(this.bitManipulator, this.value); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + public bool MoveNext() + { + if (this.bitManipulator.IsZero(this.remaining)) + { + return false; + } + + // move to the next bit + while (this.bitManipulator.IsEven(this.remaining)) + { + this.remaining = this.bitManipulator.ShiftRight(this.remaining); + this.current = this.bitManipulator.ShiftLeft(this.current); + } + + // discard this bit + this.remaining = this.bitManipulator.BitwiseAndNot( + this.remaining, + this.bitManipulator.One + ); + + return true; + } + + public void Reset() + { + this.remaining = this.value; + this.current = this.bitManipulator.One; + } + + public T Current => this.current; + + object IEnumerator.Current => this.Current; + + public void Dispose() + { + } + } +} \ No newline at end of file diff --git a/dotnet/Multiflag/Flag.cs b/dotnet/Multiflag/Flag.cs index 69e7324..18a0c75 100644 --- a/dotnet/Multiflag/Flag.cs +++ b/dotnet/Multiflag/Flag.cs @@ -1,231 +1,108 @@ -using System.ComponentModel; -using System.Diagnostics; +using System.Collections.Generic; +using System.Linq; namespace Multiflag { /// - /// A generic flag.
- /// Either use one of the built-in implementations, or inherit this class - /// to support custom flags. The 's job is - /// to implement certain methods for (see - /// ).
- /// All flags can have parent/child relationships with others flags of the - /// same type : for a flag to be included in a flag value, all of its - /// parents must be too. That means adding a flag will add all its parents - /// along with it, and removing one removes all of its children. + /// A represents some element of + /// that can be added or removed from the set. + /// When a flag is added to the set, all of its parents are added along + /// with it, and when it is removed all of its children are removed too. ///
- /// The type of the flag values. - /// The adapter that enables a to be used for flags. - public class Flag where TAdapter : IFlagValue, new() + public class Flag { - private TValue value; - private TAdapter adapter; - private HashSet> parents, children; + private readonly ISetOperations operations; + private readonly List> parents; + private readonly List> children; /// - /// Create a new flag with a value. + /// Creates a new flag. /// - /// The value of the flag. - /// The parent flags. See the class' documentation for details about parents. - public Flag(TValue value, params Flag[] parents) + internal Flag(ISetOperations operations, IEnumerable> parents) { - this.value = value; - this.adapter = new(); - this.parents = parents.ToHashSet(); - this.children = new(); + this.operations = operations; + this.parents = parents.ToList(); + this.children = new List>(); foreach (var parent in this.parents) { + if (!parent.BelongsTo(this.operations)) + { + throw new ForeignFlagException(); + } + parent.children.Add(this); } } + + internal ISetOperations Operations => this.operations; /// - /// Create a flag with no value on its own. + /// when this flag has no value on its own. /// - /// The parent flags. See the class' documentation for details about parents. - public Flag(params Flag[] parents) - { - this.adapter = new(); - this.value = this.adapter.Nothing(); - this.parents = parents.ToHashSet(); - this.children = new(); + public virtual bool IsAbstract => true; - foreach (var parent in this.parents) - { - parent.children.Add(this); - } + private bool BelongsTo(ISetOperations flagSet) + { + return this.operations == flagSet; } /// - /// Add a flag if it is not already present. + /// Add a flag if it is not already present. /// /// The flags to add it to. - /// The modified flags. - /// - /// If is reference type, the flags passed to the function may be modified in-place. - /// - public TValue AddTo(TValue flags) + /// A copy of the flags with this flag added. + public virtual TSet AddTo(TSet flags) { - TValue newValue = this.adapter.Union(flags, this.value); - foreach (var parent in this.parents) - { - newValue = parent.AddTo(newValue); - } - return newValue; + return this.parents.Aggregate( + flags, + (current, parent) => parent.AddTo(current) + ); } /// - /// See . + /// See . /// - public static TValue operator +(TValue value, Flag flag) + public static TSet operator +(TSet value, Flag flag) { return flag.AddTo(value); } /// - /// Removes a flag if it is present. + /// Removes a flag if it is present. /// /// The flags to remove it from. /// The modified flags. /// - /// If is reference type, the flags passed to the function may be modified in-place. + /// If is reference type, the flags passed to the function may be modified in-place. /// - public TValue RemoveFrom(TValue flags) + public virtual TSet RemoveFrom(TSet flags) { - TValue newValue = this.adapter.Substraction(flags, this.value); - foreach (var child in this.children) - { - newValue = child.RemoveFrom(newValue); - } - return newValue; + return this.children.Aggregate( + flags, + (current, child) => child.RemoveFrom(current) + ); } /// - /// See + /// See /// - public static TValue operator -(TValue value, Flag flag) + public static TSet operator -(TSet value, Flag flag) { return flag.RemoveFrom(value); } /// - /// Check wether that flag is included in . + /// Check whether this flag is in . /// /// The flags to search in. /// - /// if this flag is included in - /// , otherwise . - /// - public bool In(TValue flags) - { - return this.adapter.Includes(flags, this.value) && this.parents.All(parent => parent.In(flags)); - } - - /// - /// when this flag has no value and its parents are « nothing » too. - /// - /// - /// If this is , IsConcrete and IsAbstract are . - /// - public bool IsNothing => this.adapter.Equivalent(this.value, this.adapter.Nothing()) - && this.parents.All(parent => parent.IsNothing); - - /// - /// when this flag has a value. - /// - /// - /// If this is , IsNothing and IsAbstract are . - /// - public bool IsConcrete => !this.adapter.Equivalent(this.value, this.adapter.Nothing()); - - /// - /// when this flag has no value but some parents. - /// - /// - /// If this is , IsNothing and IsConcrete are . - /// - - public bool IsAbstract => this.adapter.Equivalent(this.value, this.adapter.Nothing()) - && this.parents.Any(parent => !parent.IsNothing); - - /// - /// Determines wether the two flags are equivalent when added to a set. - /// - /// The other flag to compare with this one. - /// - /// if the two flags are equivalent, otherwise. - /// - public bool PositiveEquals(Flag other) - { - TValue addedThis = this.AddTo(this.adapter.Nothing()); - TValue addedOther = other.AddTo(this.adapter.Nothing()); - - return this.adapter.Equivalent(addedThis, addedOther); - } - - private TValue NegativeUnionWith(TValue value) - { - value = this.adapter.Union(value, this.value); - foreach (var child in this.children) - { - child.NegativeUnionWith(value); - } - return value; - } - - /// - /// Determines wether the two flags are equivalent when removed from a set. - /// - /// The other flag to compare with this one. - /// - /// if the two flags are equivalent, otherwise. - /// - public bool NegativeEquals(Flag other) - { - TValue removedThis = this.NegativeUnionWith(this.adapter.Nothing()); - TValue removedOther = other.NegativeUnionWith(this.adapter.Nothing()); - - return this.adapter.Equivalent(removedThis, removedOther); - } - - /// - /// - /// The equality is checked using both PositiveEquals and NegativeEquals. - /// - public bool Equals(Flag other) - { - return this.PositiveEquals(other) && this.NegativeEquals(other); - } - - public override bool Equals(object? obj) - { - return obj is Flag other - && this.Equals(other); - } - - public override int GetHashCode() - { - return this.value!.GetHashCode(); - } - } - - public static class FlagExtensions - { - /// - /// Check wether a flag is included in that value. - /// - /// The type of the value to check. - /// A corresponding adapter. - /// The flag to check. - /// - /// if the flag is included, otherwise - /// . + /// if this flag belongs to the set of + /// , otherwise . /// - public static bool Includes(this TValue value, Flag flag) - where TAdapter : IFlagValue, new() + public virtual bool IsIn(TSet flags) { - return flag.In(value); + return this.parents.All(parent => parent.IsIn(flags)); } } } \ No newline at end of file diff --git a/dotnet/Multiflag/Flag16.cs b/dotnet/Multiflag/Flag16.cs deleted file mode 100644 index 0794d44..0000000 --- a/dotnet/Multiflag/Flag16.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Multiflag.BuiltInValueTypes; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Multiflag -{ - /// - /// A flag based on a 16-bit usingned integer (thus allowing 16 different flags). - /// - public class Flag16 : Flag - { - /// - public Flag16(ushort value, params Flag16[] parents) : base(value, parents) { } - - /// - public Flag16(params Flag16[] parents) : base(parents) { } - } - -} diff --git a/dotnet/Multiflag/Flag32.cs b/dotnet/Multiflag/Flag32.cs deleted file mode 100644 index dfca890..0000000 --- a/dotnet/Multiflag/Flag32.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Multiflag.BuiltInValueTypes; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Multiflag -{ - /// - /// A flag based on a 32-bit usingned integer (thus allowing 32 different flags). - /// - public class Flag32 : Flag - { - /// - public Flag32(uint value, params Flag32[] parents) : base(value, parents) { } - - /// - public Flag32(params Flag32[] parents) : base(parents) { } - } - -} diff --git a/dotnet/Multiflag/Flag64.cs b/dotnet/Multiflag/Flag64.cs deleted file mode 100644 index 529f0fc..0000000 --- a/dotnet/Multiflag/Flag64.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Multiflag.BuiltInValueTypes; - -namespace Multiflag -{ - /// - /// A flag based on a 64-bit usingned integer (thus allowing 64 different flags). - /// - public class Flag64 : Flag - { - /// - public Flag64(ulong value, params Flag64[] parents) : base(value, parents) { } - - /// - public Flag64(params Flag64[] parents) : base(parents) { } - } -} diff --git a/dotnet/Multiflag/Flag8.cs b/dotnet/Multiflag/Flag8.cs deleted file mode 100644 index 838b910..0000000 --- a/dotnet/Multiflag/Flag8.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Multiflag.BuiltInValueTypes; - -namespace Multiflag -{ - /// - /// A flag based on a 8-bit usingned integer (thus allowing 8 different flags). - /// - public class Flag8 : Flag - { - /// - public Flag8(byte value, params Flag8[] parents) : base(value, parents) { } - - /// - public Flag8(params Flag8[] parents) : base(parents) { } - } -} diff --git a/dotnet/Multiflag/FlagEnum.cs b/dotnet/Multiflag/FlagEnum.cs deleted file mode 100644 index 1e89ecf..0000000 --- a/dotnet/Multiflag/FlagEnum.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Multiflag.BuiltInValueTypes; - -namespace Multiflag -{ - /// - /// A flag based on an enum.
- ///
- /// - /// It only works with enums backed by an int, but you can implement - /// to support other enums. - /// - public class FlagEnum : Flag> - where TEnum : Enum - { - /// - public FlagEnum(TEnum value, params FlagEnum[] parents) : base(value, parents) { } - - /// - public FlagEnum(params FlagEnum[] parents) : base(parents) { } - } -} diff --git a/dotnet/Multiflag/FlagSet.cs b/dotnet/Multiflag/FlagSet.cs index 4a78092..32f59bf 100644 --- a/dotnet/Multiflag/FlagSet.cs +++ b/dotnet/Multiflag/FlagSet.cs @@ -1,25 +1,171 @@ -using Multiflag.BuiltInValueTypes; +using System.Collections.Generic; namespace Multiflag { /// - /// A flag based on a . This allows you to use - /// pretty much anything as a flag value. + /// Represent a group of flags, and provide methods to use + /// as a set. + /// Built-in implementations exist for unsigned integers, enums, + /// hash sets and lists. /// - /// The type of the values in the set. - /// - /// The set is modified in-place. If you do not want this behavior, make a - /// copy of your set before doing any flag operations on it. - /// - public class FlagSet : Flag, SetFlagValue> + /// The type of the flag values. + /// The type of the sets of flags. + public abstract class FlagSet : ISetOperations + where TValue : notnull { - /// - public FlagSet(HashSet value, params FlagSet[] parents) : base(value, parents) { } + private readonly Dictionary> valueFlags; - /// - public FlagSet(T value, params FlagSet[] parents) : base(new HashSet { value }, parents) { } + /// + /// Creates a new empty flag set. + /// + protected FlagSet() + { + this.valueFlags = new Dictionary>(); + } - /// - public FlagSet(params FlagSet[] parents) : base(parents) { } + /// + /// Creates a flag without a value. + /// + /// + /// Other flags required for this flag to be set. + /// + /// A flag bound to this set. + /// + /// If one of the parents doesn't belong to the same + /// . + /// + public Flag Flag(params Flag[] parents) + { + return new Flag(this, parents); + } + + /// + /// Creates a flag with a value. + /// + /// The value of the flag. + /// + /// Other flags required for this flag to be set. + /// + /// A flag bound to this set. + /// + /// If one of the parents doesn't belong to the same + /// . + /// + /// + /// If another flag has already been created with the same value. + /// + public Flag Flag(TValue value, params Flag[] parents) + { + if (this.valueFlags.ContainsKey(value)) + { + throw new ReusedFlagValueException(value); + } + + var flag = new ValueFlag(this, parents, this.WrapValue(value)); + this.valueFlags.Add(value, flag); + return flag; + } + + /// + /// Transforms a value into a set containing only that value. + /// This method may throw an exception if the value is not valid. + /// + /// The value that will be used for the flag. + protected abstract TSet WrapValue(TValue value); + + /// + /// Filters a flag set so that it only contains the flags that were + /// declared with the Flag method. If a flags is missing some + /// of its parents, it will not be included in the result. + /// + /// The set of flags to filter. + /// A new set of flags. + /// + public TSet Minimum(TSet flags) + { + TSet result = this.Empty(); + foreach (var value in this.Iterate(flags)) + { + if (this.valueFlags.TryGetValue(value, out var flag) && flag.IsIn(flags)) + { + result = flag.AddTo(result); + } + } + + return result; + } + + /// + /// Creates a copy of a flag set that will contain all the flags + /// that were declared with the Flag method. If a flag is + /// missing some of its parents in the original set, they will be + /// added to the result. + /// + /// The set of flags to filter. + /// A new set of flags. + /// + public TSet Maximum(TSet flags) + { + TSet result = this.Empty(); + foreach (var value in this.Iterate(flags)) { + if (this.valueFlags.TryGetValue(value, out var flag)) + { + result = flag.AddTo(result); + } + } + + return result; + } + + /// + /// Creates an empty set of flags. + /// + public abstract TSet Empty(); + + /// + /// Computes the union of two sets of flags. + /// + /// The first set of flags. + /// The second set of flags. + /// + /// A new set that contains the flags that appear in any of the sets. + /// + public abstract TSet Union(TSet first, TSet second); + + /// + /// Computes the intersection of two sets of flags. + /// + /// The first set of flags. + /// The second set of flags. + /// + /// A new set that contains the flags that appear in both sets. + /// + public abstract TSet Intersection(TSet first, TSet second); + + /// + /// Computes the difference of two set of flags. + /// + /// The first set of flags. + /// + /// The second set of flags (that will be subtracted from the first). + /// + /// + /// A new set that contains the flags of the first set that do not + /// appear in the second. + /// + public abstract TSet Difference(TSet first, TSet second); + + /// + /// Checks whether the first set of flags is a superset of the second. + /// + /// The first set of flags. + /// The second set of flags. + public abstract bool IsSupersetOf(TSet first, TSet second); + + /// + /// Returns an iterable over the elements of a set. + /// + /// A set of flags + public abstract IEnumerable Iterate(TSet flags); } -} +} \ No newline at end of file diff --git a/dotnet/Multiflag/ForeignFlagException.cs b/dotnet/Multiflag/ForeignFlagException.cs new file mode 100644 index 0000000..3a795aa --- /dev/null +++ b/dotnet/Multiflag/ForeignFlagException.cs @@ -0,0 +1,16 @@ +using System; + +namespace Multiflag +{ + /// + /// Exception thrown when a flag is associated with another one + /// that was created from a different . + /// + public class ForeignFlagException : ArgumentException + { + internal ForeignFlagException() : base( + "Cannot create a dependency between two flags created in different sets.") + { + } + } +} \ No newline at end of file diff --git a/dotnet/Multiflag/IFlagValue.cs b/dotnet/Multiflag/IFlagValue.cs deleted file mode 100644 index 4ae9e2e..0000000 --- a/dotnet/Multiflag/IFlagValue.cs +++ /dev/null @@ -1,64 +0,0 @@ -namespace Multiflag -{ - /// - /// An adapter to use a regular type as a flag. Built-in implementations - /// exist for , , , - /// , s and . - ///
- /// For other integers and custom enumerations, it can easily be implented - /// as such: - /// - /// Nothing() => 0 - /// Union(a, b) => a | b - /// Substraction => a & ~b - /// Includes => (a & b) == b - /// Equivalent => a == b - /// - ///
- /// The type to be used as a flag. - public interface IFlagValue - { - /// - /// Creates an empty set of flags that can only include itself. - /// - T Nothing(); - - /// - /// Returns the union of two sets of flags. - /// - /// The first set of flags. - /// The second set of flags. - /// - /// If is a reference type, the operation may - /// be done in-place on the first set.
That is, the first parameter is - /// should not be reused after being passed to that function. - ///
- T Union(T first, T second); - - /// - /// Returns the substraction af one set of flags by another. - /// - /// The first set of flags. - /// The second set of flags that is substracted to the first. - /// - /// If is a reference type, the operation may - /// be done in-place on the first set.
That is, the first parameter is - /// should not be reused after being passed to that function. - ///
- T Substraction(T first, T second); - - /// - /// Checks wether the first set of flags is a superset of the second. - /// - /// The first set of flags. - /// The second set of flags. - bool Includes(T first, T second); - - /// - /// Checks wether two sets of flags contains exactly the same same flags. - /// - /// The first set of flags. - /// The second set of flags. - bool Equivalent(T first, T second); - } -} \ No newline at end of file diff --git a/dotnet/Multiflag/ISetOperations.cs b/dotnet/Multiflag/ISetOperations.cs new file mode 100644 index 0000000..2bd8d6c --- /dev/null +++ b/dotnet/Multiflag/ISetOperations.cs @@ -0,0 +1,30 @@ +namespace Multiflag +{ + internal interface ISetOperations + { + /// + /// Creates an empty set of flags. + /// + TSet Empty(); + + /// + /// Computes the union of two sets of flags. + /// + public TSet Union(TSet first, TSet second); + + /// + /// Computes the intersection of two sets of flags. + /// + public TSet Intersection(TSet first, TSet second); + + /// + /// Computes the difference of two set of flags. + /// + public TSet Difference(TSet first, TSet second); + + /// + /// Checks whether the first set of flags is a superset of the second. + /// + public bool IsSupersetOf(TSet first, TSet second); + } +} \ No newline at end of file diff --git a/dotnet/Multiflag/InvalidBitflagValueException.cs b/dotnet/Multiflag/InvalidBitflagValueException.cs new file mode 100644 index 0000000..0e61109 --- /dev/null +++ b/dotnet/Multiflag/InvalidBitflagValueException.cs @@ -0,0 +1,15 @@ +using System; + +namespace Multiflag +{ + /// + /// Exception thrown by FlagSets that represents the flags using a binary format when + /// a flag value isn't a power of two. + /// + public class InvalidBitflagValueException : ArgumentException + { + internal InvalidBitflagValueException() : base("Flag values for bit flags must be powers of two.") + { + } + } +} \ No newline at end of file diff --git a/dotnet/Multiflag/Multiflag.csproj b/dotnet/Multiflag/Multiflag.csproj index dd6da54..9570218 100644 --- a/dotnet/Multiflag/Multiflag.csproj +++ b/dotnet/Multiflag/Multiflag.csproj @@ -1,32 +1,39 @@  - - net7.0;net6.0 - enable - enable - True - Multiflag - 1.0.0 - Louis DEVIE - - True - MIT - flag:flagging;bitflag;enum;states;permissions;features; - https://github.com/louisdevie/multiflag - README.md - A library to help you manipulate (bit)flags easily and enables you to implement inheritance between flags. - https://louisdevie.github.io/multiflag - + + netstandard2.1;net8.0;net9.0 + enable + True + True + Multiflag + 2.0.0 + Louis DEVIE + True + MIT + flag:flagging;bitflag;enum;states;permissions;features; + https://github.com/louisdevie/multiflag + README.md + A library to help you manipulate (bit)flags easily and enables you to implement inheritance between flags. + https://louisdevie.github.io/multiflag + - - - + + + - - - True - \ - - + + + + + + + True + \ + + + + + + diff --git a/dotnet/Multiflag/ReusedFlagValueException.cs b/dotnet/Multiflag/ReusedFlagValueException.cs new file mode 100644 index 0000000..517ff18 --- /dev/null +++ b/dotnet/Multiflag/ReusedFlagValueException.cs @@ -0,0 +1,16 @@ +using System; + +namespace Multiflag +{ + /// + /// Exception thrown if the method is called + /// with a value that was already used for another flag in the same . + /// + public class ReusedFlagValueException : ArgumentException + { + internal ReusedFlagValueException(object value) + : base($"The flag value {value} is already being used for another flag.") + { + } + } +} \ No newline at end of file diff --git a/dotnet/Multiflag/U32BitflagSet.cs b/dotnet/Multiflag/U32BitflagSet.cs new file mode 100644 index 0000000..f6833a1 --- /dev/null +++ b/dotnet/Multiflag/U32BitflagSet.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using Multiflag.Bitflags; +using Multiflag.Enumerators; + +namespace Multiflag +{ + /// + /// Provides bitflags based on 32-bit unsigned integers (thus allowing 32 different flags). + /// + public class U32BitflagSet : FlagSet + { + /// + protected override sealed uint WrapValue(uint value) + { + U32BitManipulator.Current.CheckPowerOfTwo(value); + return value; + } + + /// + public override sealed uint Empty() + { + return U32BitManipulator.Current.Zero; + } + + /// + public override sealed uint Union(uint first, uint second) + { + return U32BitManipulator.Current.BitwiseOr(first, second); + } + + /// + public override sealed uint Intersection(uint first, uint second) + { + return U32BitManipulator.Current.BitwiseAnd(first, second); + } + + /// + public override sealed uint Difference(uint first, uint second) + { + return U32BitManipulator.Current.BitwiseAndNot(first, second); + } + + /// + public override sealed bool IsSupersetOf(uint first, uint second) + { + return U32BitManipulator.Current.BitwiseAndEquals(first, second); + } + + /// + public override IEnumerable Iterate(uint flags) + { + return new NumericFlagEnumerator( + U32BitManipulator.Current, + flags + ); + } + } +} \ No newline at end of file diff --git a/dotnet/Multiflag/U64BitflagSet.cs b/dotnet/Multiflag/U64BitflagSet.cs new file mode 100644 index 0000000..6013ee1 --- /dev/null +++ b/dotnet/Multiflag/U64BitflagSet.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using Multiflag.Bitflags; +using Multiflag.Enumerators; + +namespace Multiflag +{ + /// + /// Provides bitflags based on 64-bit unsigned integers (thus allowing 64 different flags). + /// + public class U64BitflagSet : FlagSet + { + /// + protected override sealed ulong WrapValue(ulong value) + { + U64BitManipulator.Current.CheckPowerOfTwo(value); + return value; + } + + /// + public override sealed ulong Empty() + { + return U64BitManipulator.Current.Zero; + } + + /// + public override sealed ulong Union(ulong first, ulong second) + { + return U64BitManipulator.Current.BitwiseOr(first, second); + } + + /// + public override sealed ulong Intersection(ulong first, ulong second) + { + return U64BitManipulator.Current.BitwiseAnd(first, second); + } + + /// + public override sealed ulong Difference(ulong first, ulong second) + { + return U64BitManipulator.Current.BitwiseAndNot(first, second); + } + + /// + public override sealed bool IsSupersetOf(ulong first, ulong second) + { + return U64BitManipulator.Current.BitwiseAndEquals(first, second); + } + + /// + public override IEnumerable Iterate(ulong flags) + { + return new NumericFlagEnumerator( + U64BitManipulator.Current, + flags + ); + } + } +} \ No newline at end of file diff --git a/dotnet/Multiflag/UndefinedValueException.cs b/dotnet/Multiflag/UndefinedValueException.cs new file mode 100644 index 0000000..88b46dc --- /dev/null +++ b/dotnet/Multiflag/UndefinedValueException.cs @@ -0,0 +1,16 @@ +using System; + +namespace Multiflag +{ + /// + /// Exception thrown when a flag is created from an with a value + /// that is not explicitly declared in the enum. + /// + public class UndefinedEnumValueException : ArgumentException + { + internal UndefinedEnumValueException(object badValue) + : base($"The value {badValue} cannot be used as a flag because it isn't a member of the enum.") + { + } + } +} \ No newline at end of file diff --git a/dotnet/Multiflag/UnsupportedEnumTypeException.cs b/dotnet/Multiflag/UnsupportedEnumTypeException.cs new file mode 100644 index 0000000..ab2ca24 --- /dev/null +++ b/dotnet/Multiflag/UnsupportedEnumTypeException.cs @@ -0,0 +1,17 @@ +using System; + +namespace Multiflag +{ + /// + /// Exception thrown when an is instantiated with an enumeration type + /// that is not backed by one of , , or + /// . + /// + public class UnsupportedEnumTypeException : ArgumentException + { + internal UnsupportedEnumTypeException(Type underlyingType, string suggestedType) + : base($"Enums with an underlying type of {underlyingType} are not supported. Use {suggestedType} instead.") + { + } + } +} \ No newline at end of file diff --git a/dotnet/Multiflag/ValueFlag.cs b/dotnet/Multiflag/ValueFlag.cs new file mode 100644 index 0000000..30ea3d9 --- /dev/null +++ b/dotnet/Multiflag/ValueFlag.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; + +namespace Multiflag +{ + internal class ValueFlag : Flag + { + private readonly TSet value; + + internal ValueFlag( + ISetOperations operations, + IEnumerable> parents, + TSet value + ) : base(operations, parents) + { + this.value = value; + } + + public override bool IsAbstract => false; + + public override TSet AddTo(TSet flags) + { + return base.AddTo(this.Operations.Union(flags, this.value)); + } + + public override TSet RemoveFrom(TSet flags) + { + return base.RemoveFrom(this.Operations.Difference(flags, this.value)); + } + + public override bool IsIn(TSet flags) + { + return this.Operations.IsSupersetOf(flags, this.value) + && base.IsIn(flags); + } + } +} \ No newline at end of file diff --git a/dotnet/Tests/Base64BitflagSetTests.cs b/dotnet/Tests/Base64BitflagSetTests.cs new file mode 100644 index 0000000..9633ee1 --- /dev/null +++ b/dotnet/Tests/Base64BitflagSetTests.cs @@ -0,0 +1,159 @@ +namespace Multiflag.Tests; + +public class Base64BitflagSetTests +{ + [Fact] + public void Create() + { + var flags = new Base64BitflagSet(); + + var flag2 = flags.Flag(2); + Assert.Equal("C", "" + flag2); + + Assert.Throws(() => flags.Flag(0)); + Assert.Throws(() => flags.Flag(-2)); + } + + [Fact] + public void Union() { + var flags = new Base64BitflagSet(); + + Assert.Equal("A", flags.Union("", "")); + Assert.Equal("A", flags.Union("A", "A")); + Assert.Equal("B", flags.Union("B", "A")); + Assert.Equal("C", flags.Union("A", "C")); + Assert.Equal("D", flags.Union("B", "C")); + Assert.Equal("H", flags.Union("D", "G")); + } + + [Fact] + public void Difference() + { + var flags = new Base64BitflagSet(); + + Assert.Equal("A", flags.Difference("", "")); + Assert.Equal("A", flags.Difference("A", "A")); + Assert.Equal("B", flags.Difference("B", "A")); + Assert.Equal("B", flags.Difference("D", "G")); + Assert.Equal("E", flags.Difference("G", "D")); + Assert.Equal("IB", flags.Difference("IB", "R")); + } + + [Fact] + public void Intersection() + { + var flags = new Base64BitflagSet(); + + Assert.Equal("A", flags.Intersection("", "")); + Assert.Equal("A", flags.Intersection("A", "A")); + Assert.Equal("A", flags.Intersection("B", "A")); + Assert.Equal("A", flags.Intersection("B", "C")); + Assert.Equal("B", flags.Intersection("B", "D")); + Assert.Equal("B", flags.Intersection("L", "F")); + Assert.Equal("D", flags.Intersection("L", "H")); + } + + [Fact] + public void Iterate() + { + var flags = new Base64BitflagSet(); + + Assert.Equal([], flags.Iterate("A")); + Assert.Equal([1], flags.Iterate("B")); + Assert.Equal([2], flags.Iterate("C")); + Assert.Equal([1, 2], flags.Iterate("D")); + Assert.Equal([1, 2, 4], flags.Iterate("L")); + Assert.Equal([3, 6, 7], flags.Iterate("kB")); + } + + [Fact] + public void Minimum() + { + var flags = new Base64BitflagSet(); + var flag1 = flags.Flag(1); + var flag2 = flags.Flag(2, flag1); + var flag3 = flags.Flag(3, flag1); + var flag4 = flags.Flag(4, flag3); + + Assert.Equal("A", flags.Minimum("A")); + Assert.Equal("B", flags.Minimum("B")); + Assert.Equal("A", flags.Minimum("C")); + Assert.Equal("D", flags.Minimum("D")); + Assert.Equal("D", flags.Minimum("L")); + Assert.Equal("N", flags.Minimum("N")); + Assert.Equal("B", flags.Minimum("R")); + } + + [Fact] + public void Maximum() + { + var flags = new Base64BitflagSet(); + var flag1 = flags.Flag(1); + var flag2 = flags.Flag(2, flag1); + var flag3 = flags.Flag(3, flag1); + var flag4 = flags.Flag(4, flag3); + + Assert.Equal("A", flags.Maximum("A")); + Assert.Equal("B", flags.Maximum("B")); + Assert.Equal("D", flags.Maximum("C")); + Assert.Equal("D", flags.Maximum("D")); + Assert.Equal("P", flags.Maximum("L")); + Assert.Equal("N", flags.Maximum("N")); + Assert.Equal("B", flags.Maximum("R")); + } + + [Fact] + public void Add() + { + var flags = new Base64BitflagSet(); + var flag2 = flags.Flag(2); + var flag3 = flags.Flag(3); + var flags2And3 = flags.Flag(flag2, flag3); + + Assert.Equal("D", "B" + flag2); + Assert.Equal("F", "B" + flag3); + Assert.Equal("H", "B" + flags2And3); + } + + [Fact] + public void Remove() + { + var flags = new Base64BitflagSet(); + var flag1 = flags.Flag(1); + var flag2 = flags.Flag(2); + var flag3 = flags.Flag(3, flag1); + + Assert.Equal("C", "H" - flag1); + Assert.Equal("F", "H" - flag2); + Assert.Equal("D", "H" - flag3); + } + + [Fact] + public void IsIn() + { + var flags = new Base64BitflagSet(); + var flag1 = flags.Flag(1); + var flag2 = flags.Flag(2); + var flag3 = flags.Flag(3, flag1); + + Assert.True(flag1.IsIn("B")); + Assert.True(flag2.IsIn("D")); + Assert.False(flag3.IsIn("E")); + Assert.True(flag3.IsIn("F")); + } + + [Fact] + public void IsAbstract() + { + var flags = new Base64BitflagSet(); + var flag1 = flags.Flag(1); + var flag2 = flags.Flag(2); + var flags1And2 = flags.Flag(flag1, flag2); + var flag3 = flags.Flag(3, flags1And2); + + Assert.False(flag1.IsAbstract); + Assert.False(flag2.IsAbstract); + Assert.True(flags1And2.IsAbstract); + Assert.False(flag3.IsAbstract); + } +} \ No newline at end of file diff --git a/dotnet/Tests/Base64CodecTests.cs b/dotnet/Tests/Base64CodecTests.cs new file mode 100644 index 0000000..0c408bd --- /dev/null +++ b/dotnet/Tests/Base64CodecTests.cs @@ -0,0 +1,224 @@ +using Multiflag.Base64Format; + +namespace Multiflag.Tests; + +public class Base64CodecTests +{ + [Fact] + public void EncodeByte() + { + Assert.Equal('A', Base64Codec.EncodeByte(0)); + Assert.Equal('B', Base64Codec.EncodeByte(1)); + Assert.Equal('C', Base64Codec.EncodeByte(2)); + Assert.Equal('D', Base64Codec.EncodeByte(3)); + Assert.Equal('E', Base64Codec.EncodeByte(4)); + Assert.Equal('F', Base64Codec.EncodeByte(5)); + Assert.Equal('G', Base64Codec.EncodeByte(6)); + Assert.Equal('H', Base64Codec.EncodeByte(7)); + Assert.Equal('I', Base64Codec.EncodeByte(8)); + Assert.Equal('J', Base64Codec.EncodeByte(9)); + Assert.Equal('K', Base64Codec.EncodeByte(10)); + Assert.Equal('L', Base64Codec.EncodeByte(11)); + Assert.Equal('M', Base64Codec.EncodeByte(12)); + Assert.Equal('N', Base64Codec.EncodeByte(13)); + Assert.Equal('O', Base64Codec.EncodeByte(14)); + Assert.Equal('P', Base64Codec.EncodeByte(15)); + Assert.Equal('Q', Base64Codec.EncodeByte(16)); + Assert.Equal('R', Base64Codec.EncodeByte(17)); + Assert.Equal('S', Base64Codec.EncodeByte(18)); + Assert.Equal('T', Base64Codec.EncodeByte(19)); + Assert.Equal('U', Base64Codec.EncodeByte(20)); + Assert.Equal('V', Base64Codec.EncodeByte(21)); + Assert.Equal('W', Base64Codec.EncodeByte(22)); + Assert.Equal('X', Base64Codec.EncodeByte(23)); + Assert.Equal('Y', Base64Codec.EncodeByte(24)); + Assert.Equal('Z', Base64Codec.EncodeByte(25)); + Assert.Equal('a', Base64Codec.EncodeByte(26)); + Assert.Equal('b', Base64Codec.EncodeByte(27)); + Assert.Equal('c', Base64Codec.EncodeByte(28)); + Assert.Equal('d', Base64Codec.EncodeByte(29)); + Assert.Equal('e', Base64Codec.EncodeByte(30)); + Assert.Equal('f', Base64Codec.EncodeByte(31)); + Assert.Equal('g', Base64Codec.EncodeByte(32)); + Assert.Equal('h', Base64Codec.EncodeByte(33)); + Assert.Equal('i', Base64Codec.EncodeByte(34)); + Assert.Equal('j', Base64Codec.EncodeByte(35)); + Assert.Equal('k', Base64Codec.EncodeByte(36)); + Assert.Equal('l', Base64Codec.EncodeByte(37)); + Assert.Equal('m', Base64Codec.EncodeByte(38)); + Assert.Equal('n', Base64Codec.EncodeByte(39)); + Assert.Equal('o', Base64Codec.EncodeByte(40)); + Assert.Equal('p', Base64Codec.EncodeByte(41)); + Assert.Equal('q', Base64Codec.EncodeByte(42)); + Assert.Equal('r', Base64Codec.EncodeByte(43)); + Assert.Equal('s', Base64Codec.EncodeByte(44)); + Assert.Equal('t', Base64Codec.EncodeByte(45)); + Assert.Equal('u', Base64Codec.EncodeByte(46)); + Assert.Equal('v', Base64Codec.EncodeByte(47)); + Assert.Equal('w', Base64Codec.EncodeByte(48)); + Assert.Equal('x', Base64Codec.EncodeByte(49)); + Assert.Equal('y', Base64Codec.EncodeByte(50)); + Assert.Equal('z', Base64Codec.EncodeByte(51)); + Assert.Equal('0', Base64Codec.EncodeByte(52)); + Assert.Equal('1', Base64Codec.EncodeByte(53)); + Assert.Equal('2', Base64Codec.EncodeByte(54)); + Assert.Equal('3', Base64Codec.EncodeByte(55)); + Assert.Equal('4', Base64Codec.EncodeByte(56)); + Assert.Equal('5', Base64Codec.EncodeByte(57)); + Assert.Equal('6', Base64Codec.EncodeByte(58)); + Assert.Equal('7', Base64Codec.EncodeByte(59)); + Assert.Equal('8', Base64Codec.EncodeByte(60)); + Assert.Equal('9', Base64Codec.EncodeByte(61)); + Assert.Equal('-', Base64Codec.EncodeByte(62)); + Assert.Equal('_', Base64Codec.EncodeByte(63)); + } + + [Fact] + public void DecodeByte() + { + Assert.Equal(0, Base64Codec.DecodeByte('A')); + Assert.Equal(1, Base64Codec.DecodeByte('B')); + Assert.Equal(2, Base64Codec.DecodeByte('C')); + Assert.Equal(3, Base64Codec.DecodeByte('D')); + Assert.Equal(4, Base64Codec.DecodeByte('E')); + Assert.Equal(5, Base64Codec.DecodeByte('F')); + Assert.Equal(6, Base64Codec.DecodeByte('G')); + Assert.Equal(7, Base64Codec.DecodeByte('H')); + Assert.Equal(8, Base64Codec.DecodeByte('I')); + Assert.Equal(9, Base64Codec.DecodeByte('J')); + Assert.Equal(10, Base64Codec.DecodeByte('K')); + Assert.Equal(11, Base64Codec.DecodeByte('L')); + Assert.Equal(12, Base64Codec.DecodeByte('M')); + Assert.Equal(13, Base64Codec.DecodeByte('N')); + Assert.Equal(14, Base64Codec.DecodeByte('O')); + Assert.Equal(15, Base64Codec.DecodeByte('P')); + Assert.Equal(16, Base64Codec.DecodeByte('Q')); + Assert.Equal(17, Base64Codec.DecodeByte('R')); + Assert.Equal(18, Base64Codec.DecodeByte('S')); + Assert.Equal(19, Base64Codec.DecodeByte('T')); + Assert.Equal(20, Base64Codec.DecodeByte('U')); + Assert.Equal(21, Base64Codec.DecodeByte('V')); + Assert.Equal(22, Base64Codec.DecodeByte('W')); + Assert.Equal(23, Base64Codec.DecodeByte('X')); + Assert.Equal(24, Base64Codec.DecodeByte('Y')); + Assert.Equal(25, Base64Codec.DecodeByte('Z')); + Assert.Equal(26, Base64Codec.DecodeByte('a')); + Assert.Equal(27, Base64Codec.DecodeByte('b')); + Assert.Equal(28, Base64Codec.DecodeByte('c')); + Assert.Equal(29, Base64Codec.DecodeByte('d')); + Assert.Equal(30, Base64Codec.DecodeByte('e')); + Assert.Equal(31, Base64Codec.DecodeByte('f')); + Assert.Equal(32, Base64Codec.DecodeByte('g')); + Assert.Equal(33, Base64Codec.DecodeByte('h')); + Assert.Equal(34, Base64Codec.DecodeByte('i')); + Assert.Equal(35, Base64Codec.DecodeByte('j')); + Assert.Equal(36, Base64Codec.DecodeByte('k')); + Assert.Equal(37, Base64Codec.DecodeByte('l')); + Assert.Equal(38, Base64Codec.DecodeByte('m')); + Assert.Equal(39, Base64Codec.DecodeByte('n')); + Assert.Equal(40, Base64Codec.DecodeByte('o')); + Assert.Equal(41, Base64Codec.DecodeByte('p')); + Assert.Equal(42, Base64Codec.DecodeByte('q')); + Assert.Equal(43, Base64Codec.DecodeByte('r')); + Assert.Equal(44, Base64Codec.DecodeByte('s')); + Assert.Equal(45, Base64Codec.DecodeByte('t')); + Assert.Equal(46, Base64Codec.DecodeByte('u')); + Assert.Equal(47, Base64Codec.DecodeByte('v')); + Assert.Equal(48, Base64Codec.DecodeByte('w')); + Assert.Equal(49, Base64Codec.DecodeByte('x')); + Assert.Equal(50, Base64Codec.DecodeByte('y')); + Assert.Equal(51, Base64Codec.DecodeByte('z')); + Assert.Equal(52, Base64Codec.DecodeByte('0')); + Assert.Equal(53, Base64Codec.DecodeByte('1')); + Assert.Equal(54, Base64Codec.DecodeByte('2')); + Assert.Equal(55, Base64Codec.DecodeByte('3')); + Assert.Equal(56, Base64Codec.DecodeByte('4')); + Assert.Equal(57, Base64Codec.DecodeByte('5')); + Assert.Equal(58, Base64Codec.DecodeByte('6')); + Assert.Equal(59, Base64Codec.DecodeByte('7')); + Assert.Equal(60, Base64Codec.DecodeByte('8')); + Assert.Equal(61, Base64Codec.DecodeByte('9')); + Assert.Equal(62, Base64Codec.DecodeByte('-')); + Assert.Equal(63, Base64Codec.DecodeByte('_')); + } + + [Fact] + public void EncodeSingleFlag() + { + Assert.Equal("B", Base64Codec.EncodeSingleFlag(1)); + Assert.Equal("C", Base64Codec.EncodeSingleFlag(2)); + Assert.Equal("E", Base64Codec.EncodeSingleFlag(3)); + Assert.Equal("I", Base64Codec.EncodeSingleFlag(4)); + Assert.Equal("Q", Base64Codec.EncodeSingleFlag(5)); + Assert.Equal("g", Base64Codec.EncodeSingleFlag(6)); + Assert.Equal("AB", Base64Codec.EncodeSingleFlag(7)); + Assert.Equal("AI", Base64Codec.EncodeSingleFlag(10)); + Assert.Equal("AAAAAAAAAAAAAAAAI", Base64Codec.EncodeSingleFlag(100)); + } + + [Fact] + public void DecodesToZero() + { + Assert.True(Base64Codec.DecodesToZero("")); + Assert.True(Base64Codec.DecodesToZero("A")); + Assert.False(Base64Codec.DecodesToZero("X")); + Assert.False(Base64Codec.DecodesToZero("AX")); + Assert.True(Base64Codec.DecodesToZero("AA")); + Assert.False(Base64Codec.DecodesToZero("XA")); + } + + [Fact] + public void DecodesToSingleFlag() + { + Assert.False(Base64Codec.DecodesToSingleFlag("")); + Assert.False(Base64Codec.DecodesToSingleFlag("A")); + Assert.True(Base64Codec.DecodesToSingleFlag("B")); + Assert.True(Base64Codec.DecodesToSingleFlag("C")); + Assert.False(Base64Codec.DecodesToSingleFlag("D")); + Assert.True(Base64Codec.DecodesToSingleFlag("E")); + Assert.True(Base64Codec.DecodesToSingleFlag("I")); + Assert.True(Base64Codec.DecodesToSingleFlag("IAA")); + Assert.True(Base64Codec.DecodesToSingleFlag("AAI")); + Assert.False(Base64Codec.DecodesToSingleFlag("IAI")); + } + + [Fact] + public void BitwiseOr() + { + Assert.Equal("A", Base64Codec.BitwiseOr("", "")); + Assert.Equal("A", Base64Codec.BitwiseOr("A", "")); + Assert.Equal("B", Base64Codec.BitwiseOr("B", "")); + Assert.Equal("X", Base64Codec.BitwiseOr("X", "")); + Assert.Equal("X", Base64Codec.BitwiseOr("", "X")); + Assert.Equal("V", Base64Codec.BitwiseOr("R", "F")); + Assert.Equal("VD0", Base64Codec.BitwiseOr("RD0", "F")); + Assert.Equal("VD0", Base64Codec.BitwiseOr("RD0", "FAA")); + } + + [Fact] + public void BitwiseAndNot() + { + Assert.Equal("A", Base64Codec.BitwiseAndNot("", "")); + Assert.Equal("A", Base64Codec.BitwiseAndNot("A", "")); + Assert.Equal("B", Base64Codec.BitwiseAndNot("B", "")); + Assert.Equal("X", Base64Codec.BitwiseAndNot("X", "")); + Assert.Equal("A", Base64Codec.BitwiseAndNot("", "X")); + Assert.Equal("Q", Base64Codec.BitwiseAndNot("R", "F")); + Assert.Equal("QD0", Base64Codec.BitwiseAndNot("RD0", "F")); + Assert.Equal("QD0", Base64Codec.BitwiseAndNot("RD0", "FAA")); + Assert.Equal("E", Base64Codec.BitwiseAndNot("F", "RD0")); + } + + [Fact] + public void BitwiseAndEquals() + { + Assert.True(Base64Codec.BitwiseAndEquals("", "")); + Assert.True(Base64Codec.BitwiseAndEquals("A", "")); + Assert.True(Base64Codec.BitwiseAndEquals("", "A")); + Assert.True(Base64Codec.BitwiseAndEquals("X", "")); + Assert.False(Base64Codec.BitwiseAndEquals("", "X")); + Assert.False(Base64Codec.BitwiseAndEquals("R", "F")); + Assert.True(Base64Codec.BitwiseAndEquals("RD0", "AA0")); + Assert.True(Base64Codec.BitwiseAndEquals("RD0", "RAA")); + } +} \ No newline at end of file diff --git a/dotnet/Tests/CollectionFlagSetTests.cs b/dotnet/Tests/CollectionFlagSetTests.cs new file mode 100644 index 0000000..8c83164 --- /dev/null +++ b/dotnet/Tests/CollectionFlagSetTests.cs @@ -0,0 +1,147 @@ +using System.Collections.Immutable; + +namespace Multiflag.Tests; + +public class CollectionFlagSetTests +{ + [Fact] + public void Union() + { + var flags = new CollectionFlagSet(); + + Assert.Equal([], flags.Union([], [])); + Assert.Equal(["A"], flags.Union(["A"], [])); + Assert.Equal(["B"], flags.Union([], ["B"])); + Assert.Equal(["A", "B"], flags.Union(["A"], ["B"])); + Assert.Equal(["A", "B", "C"], flags.Union(["A", "B"], ["B", "C"])); + } + + [Fact] + public void Difference() + { + var flags = new CollectionFlagSet(); + + Assert.Equal([], flags.Difference([], [])); + Assert.Equal(["A"], flags.Difference(["A"], [])); + Assert.Equal(["A"], flags.Difference(["A", "B"], ["B", "C"])); + Assert.Equal(["C"], flags.Difference(["B", "C"], ["A", "B"])); + Assert.Equal(["D"], flags.Difference(["D"], ["A", "E"])); + } + + + [Fact] + public void Intersection() + { + var flags = new CollectionFlagSet(); + + Assert.Equal([], flags.Intersection([], [])); + Assert.Equal([], flags.Intersection(["A"], [])); + Assert.Equal([], flags.Intersection(["A"], ["B"])); + Assert.Equal(["A"], flags.Intersection(["A"], ["A", "B"])); + Assert.Equal(["A"], flags.Intersection(["A", "B", "D"], ["A", "C"])); + Assert.Equal(["A", "B"], flags.Intersection(["A", "B", "D"], ["A", "B", "C"])); + } + + [Fact] + public void Iterate() + { + var flags = new CollectionFlagSet(); + + Assert.Equal([], flags.Iterate([])); + Assert.Equal(["A"], flags.Iterate(["A"])); + Assert.Contains("A", flags.Iterate(["A", "B", "C"])); + Assert.Contains("B", flags.Iterate(["A", "B", "C"])); + Assert.Contains("C", flags.Iterate(["A", "B", "C"])); + } + + [Fact] + public void Minimum() + { + var flags = new CollectionFlagSet(); + var flagA = flags.Flag("A"); + var flagB = flags.Flag("B", flagA); + var flagC = flags.Flag("C", flagA); + var flagD = flags.Flag("D", flagC); + + Assert.Equal([], flags.Minimum([])); + Assert.Equal(["A"], flags.Minimum(["A"])); + Assert.Equal([], flags.Minimum(["B"])); + Assert.Equal(["A", "B"], flags.Minimum(["A", "B"])); + Assert.Equal(["A", "B"], flags.Minimum(["A", "B", "D"])); + Assert.Equal(["A", "C", "D"], flags.Minimum(["A", "C", "D"])); + Assert.Equal(["A"], flags.Minimum(["A", "E"])); + } + + [Fact] + public void Maximum() + { + var flags = new CollectionFlagSet(); + var flagA = flags.Flag("A"); + var flagB = flags.Flag("B", flagA); + var flagC = flags.Flag("C", flagA); + var flagD = flags.Flag("D", flagC); + + Assert.Equal([], flags.Maximum([])); + Assert.Equal(["A"], flags.Maximum(["A"])); + Assert.Equal(["B", "A"], flags.Maximum(["B"])); + Assert.Equal(["A", "B"], flags.Maximum(["A", "B"])); + Assert.Equal(["A", "B", "D", "C"], flags.Maximum(["A", "B", "D"])); + Assert.Equal(["A", "C", "D"], flags.Maximum(["A", "C", "D"])); + Assert.Equal(["A"], flags.Maximum(["A", "E"])); + } + + [Fact] + public void Add() + { + var flags = new CollectionFlagSet(); + var flagB = flags.Flag("B"); + var flagC = flags.Flag("C"); + var flagsBAndC = flags.Flag(flagB, flagC); + + Assert.Equal(ImmutableHashSet.Create("A", "B"), ImmutableHashSet.Create("A") + flagB); + Assert.Equal(ImmutableHashSet.Create("A", "C"), ImmutableHashSet.Create("A") + flagC); + Assert.Equal(ImmutableHashSet.Create("A", "B", "C"), ImmutableHashSet.Create("A") + flagsBAndC); + } + + [Fact] + public void Remove() + { + var flags = new CollectionFlagSet(); + var flagA = flags.Flag("A"); + var flagB = flags.Flag("B"); + var flagC = flags.Flag("C", flagA); + + Assert.Equal(ImmutableHashSet.Create("B"), ImmutableHashSet.Create("A", "B", "C") - flagA); + Assert.Equal(ImmutableHashSet.Create("A", "C"), ImmutableHashSet.Create("A", "B", "C") - flagB); + Assert.Equal(ImmutableHashSet.Create("A", "B"), ImmutableHashSet.Create("A", "B", "C") - flagC); + } + + [Fact] + public void IsIn() + { + var flags = new CollectionFlagSet(); + var flagA = flags.Flag("A"); + var flagB = flags.Flag("B"); + var flagC = flags.Flag("C", flagA); + + Assert.True(flagA.IsIn(ImmutableHashSet.Create("A"))); + Assert.True(flagB.IsIn(ImmutableHashSet.Create("A", "B"))); + Assert.False(flagC.IsIn(ImmutableHashSet.Create("C"))); + Assert.True(flagC.IsIn(ImmutableHashSet.Create("A", "C"))); + } + + [Fact] + public void IsAbstract() + { + var flags = new CollectionFlagSet(); + var flagA = flags.Flag("A"); + var flagB = flags.Flag("B"); + var flagsAAndB = flags.Flag(flagA, flagB); + var flagC = flags.Flag("C", flagsAAndB); + + Assert.False(flagA.IsAbstract); + Assert.False(flagB.IsAbstract); + Assert.True(flagsAAndB.IsAbstract); + Assert.False(flagC.IsAbstract); + } +} \ No newline at end of file diff --git a/dotnet/Tests/DynamicBitflagSetTests.cs b/dotnet/Tests/DynamicBitflagSetTests.cs new file mode 100644 index 0000000..7e64944 --- /dev/null +++ b/dotnet/Tests/DynamicBitflagSetTests.cs @@ -0,0 +1,164 @@ +using System.Numerics; + +namespace Multiflag.Tests; + +public class DynamicBitflagSetTests +{ + private static readonly BigInteger bigPowerOfTwo = new BigInteger(1) << 100; + + [Fact] + public void ValueNotAPowerOfTwo() + { + var flags = new DynamicBitflagSet(); + Assert.Throws(() => flags.Flag(0)); + Assert.Throws(() => flags.Flag(11)); + } + + [Fact] + public void Union() + { + var flags = new DynamicBitflagSet(); + + Assert.Equal(0, flags.Union(0, 0)); + Assert.Equal(1, flags.Union(1, 0)); + Assert.Equal(2, flags.Union(0, 2)); + Assert.Equal(3, flags.Union(1, 2)); + Assert.Equal(7, flags.Union(3, 6)); + } + + [Fact] + public void Difference() { + var flags = new DynamicBitflagSet(); + + Assert.Equal(0, flags.Difference(0, 0)); + Assert.Equal(1, flags.Difference(1, 0)); + Assert.Equal(1, flags.Difference(3, 6)); + Assert.Equal(4, flags.Difference(6, 3)); + Assert.Equal(8, flags.Difference(8, 17)); + } + + [Fact] + public void Intersection() + { + var flags = new DynamicBitflagSet(); + + Assert.Equal(0, flags.Intersection(0, 0)); + Assert.Equal(0, flags.Intersection(1, 0)); + Assert.Equal(0, flags.Intersection(1, 2)); + Assert.Equal(1, flags.Intersection(1, 3)); + Assert.Equal(1, flags.Intersection(11, 5)); + Assert.Equal(3, flags.Intersection(11, 7)); + } + + [Fact] + public void Iterate() + { + var flags = new DynamicBitflagSet(); + + Assert.Equal([], flags.Iterate(0)); + Assert.Equal([1], flags.Iterate(1)); + Assert.Equal([2], flags.Iterate(2)); + Assert.Equal([1, 2], flags.Iterate(3)); + Assert.Equal([1, 2, 8], flags.Iterate(11)); + Assert.Equal([4, 32, 64], flags.Iterate(100)); + } + + [Fact] + public void Minimum() + { + var flags = new DynamicBitflagSet(); + var flag1 = flags.Flag(1); + var flag2 = flags.Flag(2, flag1); + var flag4 = flags.Flag(4, flag1); + var flag8 = flags.Flag(8, flag4); + + Assert.Equal(0, flags.Minimum(0)); + Assert.Equal(1, flags.Minimum(1)); + Assert.Equal(0, flags.Minimum(2)); + Assert.Equal(3, flags.Minimum(3)); + Assert.Equal(3, flags.Minimum(11)); + Assert.Equal(13, flags.Minimum(13)); + Assert.Equal(1, flags.Minimum(17)); + } + + [Fact] + public void Maximum() + { + var flags = new DynamicBitflagSet(); + var flag1 = flags.Flag(1); + var flag2 = flags.Flag(2, flag1); + var flag4 = flags.Flag(4, flag1); + var flag8 = flags.Flag(8, flag4); + + Assert.Equal(0, flags.Maximum(0)); + Assert.Equal(1, flags.Maximum(1)); + Assert.Equal(3, flags.Maximum(2)); + Assert.Equal(3, flags.Maximum(3)); + Assert.Equal(15, flags.Maximum(11)); + Assert.Equal(13, flags.Maximum(13)); + Assert.Equal(1, flags.Maximum(17)); + } + + [Fact] + public void Add() + { + var flags = new DynamicBitflagSet(); + var flag2 = flags.Flag(2); + var flag4 = flags.Flag(4); + var flags2And4 = flags.Flag(flag2, flag4); + var flag100 = flags.Flag(bigPowerOfTwo); + + Assert.Equal(3, 1 + flag2); + Assert.Equal(5, 1 + flag4); + Assert.Equal(7, 1 + flags2And4); + Assert.Equal(bigPowerOfTwo + 1, 1 + flag100); + Assert.Equal(bigPowerOfTwo + 2, bigPowerOfTwo + flag2); + } + + [Fact] + public void Remove() + { + var flags = new DynamicBitflagSet(); + var flag1 = flags.Flag(1); + var flag2 = flags.Flag(2); + var flag4 = flags.Flag(4, flag1); + var flag100 = flags.Flag(bigPowerOfTwo); + + Assert.Equal(2, 7 - flag1); + Assert.Equal(5, 7 - flag2); + Assert.Equal(3, 7 - flag4); + Assert.Equal(2, bigPowerOfTwo + 2 - flag100); + Assert.Equal(2, 2 - flag100); + Assert.Equal(bigPowerOfTwo - 6, bigPowerOfTwo - 1 - flag1); + } + + [Fact] + public void IsIn() + { + var flags = new DynamicBitflagSet(); + var flag1 = flags.Flag(1); + var flag2 = flags.Flag(2); + var flag4 = flags.Flag(4, flag1); + + Assert.True(flag1.IsIn(1)); + Assert.True(flag2.IsIn(3)); + Assert.False(flag4.IsIn(4)); + Assert.True(flag4.IsIn(5)); + Assert.False(flag4.IsIn(bigPowerOfTwo + 1)); + } + + [Fact] + public void IsAbstract() + { + var flags = new DynamicBitflagSet(); + var flag1 = flags.Flag(1); + var flag2 = flags.Flag(2); + var flags1And2 = flags.Flag(flag1, flag2); + var flag4 = flags.Flag(4, flags1And2); + + Assert.False(flag1.IsAbstract); + Assert.False(flag2.IsAbstract); + Assert.True(flags1And2.IsAbstract); + Assert.False(flag4.IsAbstract); + } +} \ No newline at end of file diff --git a/dotnet/Tests/EnumBitflagSetTests.cs b/dotnet/Tests/EnumBitflagSetTests.cs new file mode 100644 index 0000000..dd33078 --- /dev/null +++ b/dotnet/Tests/EnumBitflagSetTests.cs @@ -0,0 +1,213 @@ +namespace Multiflag.Tests; + +public class EnumBitflagSetTests +{ + [Fact] + public void SignedEnumsAreUnsupported() + { + Assert.Throws(() => new EnumBitflagSet()); + Assert.Throws(() => new EnumBitflagSet()); + Assert.Throws(() => new EnumBitflagSet()); + Assert.Throws(() => new EnumBitflagSet()); + Assert.Throws(() => new EnumBitflagSet()); + Assert.Throws(() => new EnumBitflagSet()); + } + + [Fact] + public void ValueNotAPowerOfTwo() + { + var flags = new EnumBitflagSet(); + Assert.Throws(() => flags.Flag(0)); + Assert.Throws(() => flags.Flag((U32Enum)11)); + } + + [Fact] + public void ValueNotDefined() + { + var flags = new EnumBitflagSet(); + Assert.Throws(() => flags.Flag((U32Enum)256)); + } + + [Fact] + public void Union() + { + var flags = new EnumBitflagSet(); + + Assert.Equal((U32Enum)0, flags.Union(0, 0)); + Assert.Equal(U32Enum.A, flags.Union(U32Enum.A, 0)); + Assert.Equal(U32Enum.B, flags.Union(0, U32Enum.B)); + Assert.Equal((U32Enum)3, flags.Union(U32Enum.A, U32Enum.B)); + Assert.Equal((U32Enum)7, flags.Union((U32Enum)3, (U32Enum)6)); + } + + [Fact] + public void Difference() { + var flags = new EnumBitflagSet(); + + Assert.Equal((U32Enum)0, flags.Difference(0, 0)); + Assert.Equal(U32Enum.A, flags.Difference(U32Enum.A, 0)); + Assert.Equal(U32Enum.A, flags.Difference((U32Enum)3, (U32Enum)6)); + Assert.Equal(U32Enum.C, flags.Difference((U32Enum)6, (U32Enum)3)); + Assert.Equal(U32Enum.D, flags.Difference(U32Enum.D, (U32Enum)17)); + } + + [Fact] + public void Intersection() + { + var flags = new EnumBitflagSet(); + + Assert.Equal((U32Enum)0, flags.Intersection(0, 0)); + Assert.Equal((U32Enum)0, flags.Intersection(U32Enum.A, 0)); + Assert.Equal((U32Enum)0, flags.Intersection(U32Enum.A, U32Enum.B)); + Assert.Equal(U32Enum.A, flags.Intersection(U32Enum.A, (U32Enum)3)); + Assert.Equal(U32Enum.A, flags.Intersection((U32Enum)11, (U32Enum)5)); + Assert.Equal((U32Enum)3, flags.Intersection((U32Enum)11, (U32Enum)7)); + } + + [Fact] + public void Iterate() + { + var flags = new EnumBitflagSet(); + + Assert.Equal([], flags.Iterate(0)); + Assert.Equal([U32Enum.A], flags.Iterate(U32Enum.A)); + Assert.Equal([U32Enum.B], flags.Iterate(U32Enum.B)); + Assert.Equal([U32Enum.A, U32Enum.B], flags.Iterate((U32Enum)3)); + Assert.Equal([U32Enum.A, U32Enum.B, U32Enum.D], flags.Iterate((U32Enum)11)); + Assert.Equal([U32Enum.C, U32Enum.F, U32Enum.G], flags.Iterate((U32Enum)100)); + } + + [Fact] + public void Minimum() + { + var flags = new EnumBitflagSet(); + var flag1 = flags.Flag(U32Enum.A); + var flag2 = flags.Flag(U32Enum.B, flag1); + var flag4 = flags.Flag(U32Enum.C, flag1); + var flag8 = flags.Flag(U32Enum.D, flag4); + + Assert.Equal((U32Enum)0, flags.Minimum(0)); + Assert.Equal(U32Enum.A, flags.Minimum(U32Enum.A)); + Assert.Equal((U32Enum)0, flags.Minimum(U32Enum.B)); + Assert.Equal((U32Enum)3, flags.Minimum((U32Enum)3)); + Assert.Equal((U32Enum)3, flags.Minimum((U32Enum)11)); + Assert.Equal((U32Enum)13, flags.Minimum((U32Enum)13)); + Assert.Equal(U32Enum.A, flags.Minimum((U32Enum)17)); + } + + [Fact] + public void Maximum() + { + var flags = new EnumBitflagSet(); + var flag1 = flags.Flag(U32Enum.A); + var flag2 = flags.Flag(U32Enum.B, flag1); + var flag4 = flags.Flag(U32Enum.C, flag1); + var flag8 = flags.Flag(U32Enum.D, flag4); + + Assert.Equal((U32Enum)0, flags.Maximum(0)); + Assert.Equal(U32Enum.A, flags.Maximum(U32Enum.A)); + Assert.Equal((U32Enum)3, flags.Maximum(U32Enum.B)); + Assert.Equal((U32Enum)3, flags.Maximum((U32Enum)3)); + Assert.Equal((U32Enum)15, flags.Maximum((U32Enum)11)); + Assert.Equal((U32Enum)13, flags.Maximum((U32Enum)13)); + Assert.Equal(U32Enum.A, flags.Maximum((U32Enum)17)); + } + + [Fact] + public void Add() + { + var flags = new EnumBitflagSet(); + var flagB = flags.Flag(U32Enum.B); + var flagC = flags.Flag(U32Enum.C); + var flags2And4 = flags.Flag(flagB, flagC); + + Assert.Equal((U32Enum)3, U32Enum.A + flagB); + Assert.Equal((U32Enum)5, U32Enum.A + flagC); + Assert.Equal((U32Enum)7, U32Enum.A + flags2And4); + } + + [Fact] + public void Add64Bit() + { + var flags = new EnumBitflagSet(); + var flagB = flags.Flag(U64Enum.B); + var flagC = flags.Flag(U64Enum.C); + var flags2And4 = flags.Flag(flagB, flagC); + + Assert.Equal((U64Enum)3, U64Enum.A + flagB); + Assert.Equal((U64Enum)5, U64Enum.A + flagC); + Assert.Equal((U64Enum)7, U64Enum.A + flags2And4); + } + + [Fact] + public void Remove() + { + var flags = new EnumBitflagSet(); + var flagA = flags.Flag(U32Enum.A); + var flagB = flags.Flag(U32Enum.B); + var flagC = flags.Flag(U32Enum.C, flagA); + + Assert.Equal(U32Enum.B, (U32Enum)7 - flagA); + Assert.Equal((U32Enum)5, (U32Enum)7 - flagB); + Assert.Equal((U32Enum)3, (U32Enum)7 - flagC); + } + + [Fact] + public void IsIn() + { + var flags = new EnumBitflagSet(); + var flagA = flags.Flag(U32Enum.A); + var flagB = flags.Flag(U32Enum.B); + var flagC = flags.Flag(U32Enum.C, flagA); + + Assert.True(flagA.IsIn(U32Enum.A)); + Assert.True(flagB.IsIn((U32Enum)3)); + Assert.False(flagC.IsIn(U32Enum.B)); + Assert.True(flagC.IsIn((U32Enum)5)); + } + + [Fact] + public void IsAbstract() + { + var flags = new EnumBitflagSet(); + var flagA = flags.Flag(U32Enum.A); + var flagB = flags.Flag(U32Enum.B); + var flags1And2 = flags.Flag(flagA, flagB); + var flagC = flags.Flag(U32Enum.C, flags1And2); + + Assert.False(flagA.IsAbstract); + Assert.False(flagB.IsAbstract); + Assert.True(flags1And2.IsAbstract); + Assert.False(flagC.IsAbstract); + } + + private enum S8Enum : sbyte; + + private enum U8Enum : byte; + + private enum S16Enum : short; + + private enum U16Enum : ushort; + + private enum S32Enum; + + private enum U32Enum : uint + { + A = 1, + B = 2, + C = 4, + D = 8, + E = 16, + F = 32, + G = 64, + } + + private enum S64Enum : long; + + private enum U64Enum : ulong + { + A = 1, + B = 2, + C = 4 + } +} \ No newline at end of file diff --git a/dotnet/Tests/Flag16Tests.cs b/dotnet/Tests/Flag16Tests.cs deleted file mode 100644 index 3e86310..0000000 --- a/dotnet/Tests/Flag16Tests.cs +++ /dev/null @@ -1,182 +0,0 @@ -using Multiflag.BuiltInValueTypes; - -namespace Tests -{ - public class Flag16Tests - { - [Fact] - public void Addition() - { - Flag16 flag1 = new Flag16(); - Flag16 flag2 = new Flag16(1); - Flag16 flag3 = new Flag16(2); - Flag16 flag4 = new Flag16(4, flag2); - - ushort flags = 0; - Assert.Equal(0, flags + flag1); - Assert.Equal(1, flags + flag2); - Assert.Equal(2, flags + flag3); - Assert.Equal(5, flags + flag4); - - flags = 2; - Assert.Equal(2, flags + flag1); - Assert.Equal(3, flags + flag2); - Assert.Equal(2, flags + flag3); - Assert.Equal(7, flags + flag4); - - flags = 255; - Assert.Equal(255, flags + flag1); - Assert.Equal(255, flags + flag2); - Assert.Equal(255, flags + flag3); - Assert.Equal(255, flags + flag4); - } - - [Fact] - public void Substraction() - { - Flag16 flag1 = new Flag16(); - Flag16 flag2 = new Flag16(1); - Flag16 flag3 = new Flag16(2); - Flag16 flag4 = new Flag16(4, flag2); - - ushort flags = 0; - Assert.Equal(0, flags - flag1); - Assert.Equal(0, flags - flag2); - Assert.Equal(0, flags - flag3); - Assert.Equal(0, flags - flag4); - - flags = 3; - Assert.Equal(3, flags - flag1); - Assert.Equal(2, flags - flag2); - Assert.Equal(1, flags - flag3); - Assert.Equal(3, flags - flag4); - - flags = 255; - Assert.Equal(255, flags - flag1); - Assert.Equal(250, flags - flag2); - Assert.Equal(253, flags - flag3); - Assert.Equal(251, flags - flag4); - } - - [Fact] - public void Includes() - { - Flag16 flag1 = new Flag16(); - Flag16 flag2 = new Flag16(1); - Flag16 flag3 = new Flag16(2); - Flag16 flag4 = new Flag16(4, flag2); - - ushort flags = 0; - Assert.True(flags.Includes(flag1)); - Assert.False(flags.Includes(flag2)); - Assert.False(flags.Includes(flag3)); - Assert.False(flags.Includes(flag4)); - - flags = 6; - Assert.True(flags.Includes(flag1)); - Assert.False(flags.Includes(flag2)); - Assert.True(flags.Includes(flag3)); - Assert.False(flags.Includes(flag4)); - - flags = 7; - Assert.True(flags.Includes(flag1)); - Assert.True(flags.Includes(flag2)); - Assert.True(flags.Includes(flag3)); - Assert.True(flags.Includes(flag4)); - } - - [Fact] - public void Properties() - { - Flag16 flag1 = new Flag16(); - Flag16 flag2 = new Flag16(1); - Flag16 flag3 = new Flag16(2, flag1); - Flag16 flag4 = new Flag16(flag1); - Flag16 flag5 = new Flag16(4, flag2); - Flag16 flag6 = new Flag16(flag2); - Flag16 flag7 = new Flag16(flag4, flag6); - - Assert.True(flag1.IsNothing); - Assert.False(flag2.IsNothing); - Assert.False(flag3.IsNothing); - Assert.True(flag4.IsNothing); - Assert.False(flag5.IsNothing); - Assert.False(flag6.IsNothing); - Assert.False(flag7.IsNothing); - - Assert.False(flag1.IsConcrete); - Assert.True(flag2.IsConcrete); - Assert.True(flag3.IsConcrete); - Assert.False(flag4.IsConcrete); - Assert.True(flag5.IsConcrete); - Assert.False(flag6.IsConcrete); - Assert.False(flag7.IsConcrete); - - Assert.False(flag1.IsAbstract); - Assert.False(flag2.IsAbstract); - Assert.False(flag3.IsAbstract); - Assert.False(flag4.IsAbstract); - Assert.False(flag5.IsAbstract); - Assert.True(flag6.IsAbstract); - Assert.True(flag7.IsAbstract); - } - - [Fact] - public void PositiveEquality() - { - Flag16 flag1 = new Flag16(); - Flag16 flag2 = new Flag16(1); - Flag16 flag3 = new Flag16(2, flag2); - Flag16 flag4 = new Flag16(flag2); - - Assert.True(flag1.PositiveEquals(flag1)); - Assert.False(flag1.PositiveEquals(flag2)); - Assert.False(flag1.PositiveEquals(flag3)); - Assert.False(flag1.PositiveEquals(flag4)); - - Assert.False(flag2.PositiveEquals(flag1)); - Assert.True(flag2.PositiveEquals(flag2)); - Assert.False(flag2.PositiveEquals(flag3)); - Assert.True(flag2.PositiveEquals(flag4)); - - Assert.False(flag3.PositiveEquals(flag1)); - Assert.False(flag3.PositiveEquals(flag2)); - Assert.True(flag3.PositiveEquals(flag3)); - Assert.False(flag3.PositiveEquals(flag4)); - - Assert.False(flag4.PositiveEquals(flag1)); - Assert.True(flag4.PositiveEquals(flag2)); - Assert.False(flag4.PositiveEquals(flag3)); - Assert.True(flag4.PositiveEquals(flag4)); - } - - [Fact] - public void NegativeEquality() - { - Flag16 flag1 = new Flag16(); - Flag16 flag2 = new Flag16(1); - Flag16 flag3 = new Flag16(2, flag2); - Flag16 flag4 = new Flag16(flag2); - - Assert.True(flag1.NegativeEquals(flag1)); - Assert.False(flag1.NegativeEquals(flag2)); - Assert.False(flag1.NegativeEquals(flag3)); - Assert.True(flag1.NegativeEquals(flag4)); - - Assert.False(flag2.NegativeEquals(flag1)); - Assert.True(flag2.NegativeEquals(flag2)); - Assert.False(flag2.NegativeEquals(flag3)); - Assert.False(flag2.NegativeEquals(flag4)); - - Assert.False(flag3.NegativeEquals(flag1)); - Assert.False(flag3.NegativeEquals(flag2)); - Assert.True(flag3.NegativeEquals(flag3)); - Assert.False(flag3.NegativeEquals(flag4)); - - Assert.True(flag4.NegativeEquals(flag1)); - Assert.False(flag4.NegativeEquals(flag2)); - Assert.False(flag4.NegativeEquals(flag3)); - Assert.True(flag4.NegativeEquals(flag4)); - } - } -} \ No newline at end of file diff --git a/dotnet/Tests/Flag32Tests.cs b/dotnet/Tests/Flag32Tests.cs deleted file mode 100644 index b97bd72..0000000 --- a/dotnet/Tests/Flag32Tests.cs +++ /dev/null @@ -1,182 +0,0 @@ -using Multiflag.BuiltInValueTypes; - -namespace Tests -{ - public class Flag32Tests - { - [Fact] - public void Addition() - { - Flag32 flag1 = new Flag32(); - Flag32 flag2 = new Flag32(1); - Flag32 flag3 = new Flag32(2); - Flag32 flag4 = new Flag32(4, flag2); - - uint flags = 0; - Assert.Equal(0u, flags + flag1); - Assert.Equal(1u, flags + flag2); - Assert.Equal(2u, flags + flag3); - Assert.Equal(5u, flags + flag4); - - flags = 2; - Assert.Equal(2u, flags + flag1); - Assert.Equal(3u, flags + flag2); - Assert.Equal(2u, flags + flag3); - Assert.Equal(7u, flags + flag4); - - flags = 255; - Assert.Equal(255u, flags + flag1); - Assert.Equal(255u, flags + flag2); - Assert.Equal(255u, flags + flag3); - Assert.Equal(255u, flags + flag4); - } - - [Fact] - public void Substraction() - { - Flag32 flag1 = new Flag32(); - Flag32 flag2 = new Flag32(1); - Flag32 flag3 = new Flag32(2); - Flag32 flag4 = new Flag32(4, flag2); - - uint flags = 0; - Assert.Equal(0u, flags - flag1); - Assert.Equal(0u, flags - flag2); - Assert.Equal(0u, flags - flag3); - Assert.Equal(0u, flags - flag4); - - flags = 3; - Assert.Equal(3u, flags - flag1); - Assert.Equal(2u, flags - flag2); - Assert.Equal(1u, flags - flag3); - Assert.Equal(3u, flags - flag4); - - flags = 255; - Assert.Equal(255u, flags - flag1); - Assert.Equal(250u, flags - flag2); - Assert.Equal(253u, flags - flag3); - Assert.Equal(251u, flags - flag4); - } - - [Fact] - public void Includes() - { - Flag32 flag1 = new Flag32(); - Flag32 flag2 = new Flag32(1); - Flag32 flag3 = new Flag32(2); - Flag32 flag4 = new Flag32(4, flag2); - - uint flags = 0; - Assert.True(flags.Includes(flag1)); - Assert.False(flags.Includes(flag2)); - Assert.False(flags.Includes(flag3)); - Assert.False(flags.Includes(flag4)); - - flags = 6; - Assert.True(flags.Includes(flag1)); - Assert.False(flags.Includes(flag2)); - Assert.True(flags.Includes(flag3)); - Assert.False(flags.Includes(flag4)); - - flags = 7; - Assert.True(flags.Includes(flag1)); - Assert.True(flags.Includes(flag2)); - Assert.True(flags.Includes(flag3)); - Assert.True(flags.Includes(flag4)); - } - - [Fact] - public void Properties() - { - Flag32 flag1 = new Flag32(); - Flag32 flag2 = new Flag32(1); - Flag32 flag3 = new Flag32(2, flag1); - Flag32 flag4 = new Flag32(flag1); - Flag32 flag5 = new Flag32(4, flag2); - Flag32 flag6 = new Flag32(flag2); - Flag32 flag7 = new Flag32(flag4, flag6); - - Assert.True(flag1.IsNothing); - Assert.False(flag2.IsNothing); - Assert.False(flag3.IsNothing); - Assert.True(flag4.IsNothing); - Assert.False(flag5.IsNothing); - Assert.False(flag6.IsNothing); - Assert.False(flag7.IsNothing); - - Assert.False(flag1.IsConcrete); - Assert.True(flag2.IsConcrete); - Assert.True(flag3.IsConcrete); - Assert.False(flag4.IsConcrete); - Assert.True(flag5.IsConcrete); - Assert.False(flag6.IsConcrete); - Assert.False(flag7.IsConcrete); - - Assert.False(flag1.IsAbstract); - Assert.False(flag2.IsAbstract); - Assert.False(flag3.IsAbstract); - Assert.False(flag4.IsAbstract); - Assert.False(flag5.IsAbstract); - Assert.True(flag6.IsAbstract); - Assert.True(flag7.IsAbstract); - } - - [Fact] - public void PositiveEquality() - { - Flag32 flag1 = new Flag32(); - Flag32 flag2 = new Flag32(1); - Flag32 flag3 = new Flag32(2, flag2); - Flag32 flag4 = new Flag32(flag2); - - Assert.True(flag1.PositiveEquals(flag1)); - Assert.False(flag1.PositiveEquals(flag2)); - Assert.False(flag1.PositiveEquals(flag3)); - Assert.False(flag1.PositiveEquals(flag4)); - - Assert.False(flag2.PositiveEquals(flag1)); - Assert.True(flag2.PositiveEquals(flag2)); - Assert.False(flag2.PositiveEquals(flag3)); - Assert.True(flag2.PositiveEquals(flag4)); - - Assert.False(flag3.PositiveEquals(flag1)); - Assert.False(flag3.PositiveEquals(flag2)); - Assert.True(flag3.PositiveEquals(flag3)); - Assert.False(flag3.PositiveEquals(flag4)); - - Assert.False(flag4.PositiveEquals(flag1)); - Assert.True(flag4.PositiveEquals(flag2)); - Assert.False(flag4.PositiveEquals(flag3)); - Assert.True(flag4.PositiveEquals(flag4)); - } - - [Fact] - public void NegativeEquality() - { - Flag32 flag1 = new Flag32(); - Flag32 flag2 = new Flag32(1); - Flag32 flag3 = new Flag32(2, flag2); - Flag32 flag4 = new Flag32(flag2); - - Assert.True(flag1.NegativeEquals(flag1)); - Assert.False(flag1.NegativeEquals(flag2)); - Assert.False(flag1.NegativeEquals(flag3)); - Assert.True(flag1.NegativeEquals(flag4)); - - Assert.False(flag2.NegativeEquals(flag1)); - Assert.True(flag2.NegativeEquals(flag2)); - Assert.False(flag2.NegativeEquals(flag3)); - Assert.False(flag2.NegativeEquals(flag4)); - - Assert.False(flag3.NegativeEquals(flag1)); - Assert.False(flag3.NegativeEquals(flag2)); - Assert.True(flag3.NegativeEquals(flag3)); - Assert.False(flag3.NegativeEquals(flag4)); - - Assert.True(flag4.NegativeEquals(flag1)); - Assert.False(flag4.NegativeEquals(flag2)); - Assert.False(flag4.NegativeEquals(flag3)); - Assert.True(flag4.NegativeEquals(flag4)); - } - } -} \ No newline at end of file diff --git a/dotnet/Tests/Flag64Tests.cs b/dotnet/Tests/Flag64Tests.cs deleted file mode 100644 index fea3957..0000000 --- a/dotnet/Tests/Flag64Tests.cs +++ /dev/null @@ -1,182 +0,0 @@ -using Multiflag.BuiltInValueTypes; - -namespace Tests -{ - public class Flag64Tests - { - [Fact] - public void Addition() - { - Flag64 flag1 = new Flag64(); - Flag64 flag2 = new Flag64(1); - Flag64 flag3 = new Flag64(2); - Flag64 flag4 = new Flag64(4, flag2); - - ulong flags = 0; - Assert.Equal(0u, flags + flag1); - Assert.Equal(1u, flags + flag2); - Assert.Equal(2u, flags + flag3); - Assert.Equal(5u, flags + flag4); - - flags = 2; - Assert.Equal(2u, flags + flag1); - Assert.Equal(3u, flags + flag2); - Assert.Equal(2u, flags + flag3); - Assert.Equal(7u, flags + flag4); - - flags = 255; - Assert.Equal(255u, flags + flag1); - Assert.Equal(255u, flags + flag2); - Assert.Equal(255u, flags + flag3); - Assert.Equal(255u, flags + flag4); - } - - [Fact] - public void Substraction() - { - Flag64 flag1 = new Flag64(); - Flag64 flag2 = new Flag64(1); - Flag64 flag3 = new Flag64(2); - Flag64 flag4 = new Flag64(4, flag2); - - ulong flags = 0; - Assert.Equal(0u, flags - flag1); - Assert.Equal(0u, flags - flag2); - Assert.Equal(0u, flags - flag3); - Assert.Equal(0u, flags - flag4); - - flags = 3; - Assert.Equal(3u, flags - flag1); - Assert.Equal(2u, flags - flag2); - Assert.Equal(1u, flags - flag3); - Assert.Equal(3u, flags - flag4); - - flags = 255; - Assert.Equal(255u, flags - flag1); - Assert.Equal(250u, flags - flag2); - Assert.Equal(253u, flags - flag3); - Assert.Equal(251u, flags - flag4); - } - - [Fact] - public void Includes() - { - Flag64 flag1 = new Flag64(); - Flag64 flag2 = new Flag64(1); - Flag64 flag3 = new Flag64(2); - Flag64 flag4 = new Flag64(4, flag2); - - ulong flags = 0; - Assert.True(flags.Includes(flag1)); - Assert.False(flags.Includes(flag2)); - Assert.False(flags.Includes(flag3)); - Assert.False(flags.Includes(flag4)); - - flags = 6; - Assert.True(flags.Includes(flag1)); - Assert.False(flags.Includes(flag2)); - Assert.True(flags.Includes(flag3)); - Assert.False(flags.Includes(flag4)); - - flags = 7; - Assert.True(flags.Includes(flag1)); - Assert.True(flags.Includes(flag2)); - Assert.True(flags.Includes(flag3)); - Assert.True(flags.Includes(flag4)); - } - - [Fact] - public void Properties() - { - Flag64 flag1 = new Flag64(); - Flag64 flag2 = new Flag64(1); - Flag64 flag3 = new Flag64(2, flag1); - Flag64 flag4 = new Flag64(flag1); - Flag64 flag5 = new Flag64(4, flag2); - Flag64 flag6 = new Flag64(flag2); - Flag64 flag7 = new Flag64(flag4, flag6); - - Assert.True(flag1.IsNothing); - Assert.False(flag2.IsNothing); - Assert.False(flag3.IsNothing); - Assert.True(flag4.IsNothing); - Assert.False(flag5.IsNothing); - Assert.False(flag6.IsNothing); - Assert.False(flag7.IsNothing); - - Assert.False(flag1.IsConcrete); - Assert.True(flag2.IsConcrete); - Assert.True(flag3.IsConcrete); - Assert.False(flag4.IsConcrete); - Assert.True(flag5.IsConcrete); - Assert.False(flag6.IsConcrete); - Assert.False(flag7.IsConcrete); - - Assert.False(flag1.IsAbstract); - Assert.False(flag2.IsAbstract); - Assert.False(flag3.IsAbstract); - Assert.False(flag4.IsAbstract); - Assert.False(flag5.IsAbstract); - Assert.True(flag6.IsAbstract); - Assert.True(flag7.IsAbstract); - } - - [Fact] - public void PositiveEquality() - { - Flag64 flag1 = new Flag64(); - Flag64 flag2 = new Flag64(1); - Flag64 flag3 = new Flag64(2, flag2); - Flag64 flag4 = new Flag64(flag2); - - Assert.True(flag1.PositiveEquals(flag1)); - Assert.False(flag1.PositiveEquals(flag2)); - Assert.False(flag1.PositiveEquals(flag3)); - Assert.False(flag1.PositiveEquals(flag4)); - - Assert.False(flag2.PositiveEquals(flag1)); - Assert.True(flag2.PositiveEquals(flag2)); - Assert.False(flag2.PositiveEquals(flag3)); - Assert.True(flag2.PositiveEquals(flag4)); - - Assert.False(flag3.PositiveEquals(flag1)); - Assert.False(flag3.PositiveEquals(flag2)); - Assert.True(flag3.PositiveEquals(flag3)); - Assert.False(flag3.PositiveEquals(flag4)); - - Assert.False(flag4.PositiveEquals(flag1)); - Assert.True(flag4.PositiveEquals(flag2)); - Assert.False(flag4.PositiveEquals(flag3)); - Assert.True(flag4.PositiveEquals(flag4)); - } - - [Fact] - public void NegativeEquality() - { - Flag64 flag1 = new Flag64(); - Flag64 flag2 = new Flag64(1); - Flag64 flag3 = new Flag64(2, flag2); - Flag64 flag4 = new Flag64(flag2); - - Assert.True(flag1.NegativeEquals(flag1)); - Assert.False(flag1.NegativeEquals(flag2)); - Assert.False(flag1.NegativeEquals(flag3)); - Assert.True(flag1.NegativeEquals(flag4)); - - Assert.False(flag2.NegativeEquals(flag1)); - Assert.True(flag2.NegativeEquals(flag2)); - Assert.False(flag2.NegativeEquals(flag3)); - Assert.False(flag2.NegativeEquals(flag4)); - - Assert.False(flag3.NegativeEquals(flag1)); - Assert.False(flag3.NegativeEquals(flag2)); - Assert.True(flag3.NegativeEquals(flag3)); - Assert.False(flag3.NegativeEquals(flag4)); - - Assert.True(flag4.NegativeEquals(flag1)); - Assert.False(flag4.NegativeEquals(flag2)); - Assert.False(flag4.NegativeEquals(flag3)); - Assert.True(flag4.NegativeEquals(flag4)); - } - } -} \ No newline at end of file diff --git a/dotnet/Tests/Flag8Tests.cs b/dotnet/Tests/Flag8Tests.cs deleted file mode 100644 index 2b01849..0000000 --- a/dotnet/Tests/Flag8Tests.cs +++ /dev/null @@ -1,182 +0,0 @@ -using Multiflag.BuiltInValueTypes; - -namespace Tests -{ - public class Flag8Tests - { - [Fact] - public void Addition() - { - Flag8 flag1 = new Flag8(); - Flag8 flag2 = new Flag8(1); - Flag8 flag3 = new Flag8(2); - Flag8 flag4 = new Flag8(4, flag2); - - byte flags = 0; - Assert.Equal(0, flags + flag1); - Assert.Equal(1, flags + flag2); - Assert.Equal(2, flags + flag3); - Assert.Equal(5, flags + flag4); - - flags = 2; - Assert.Equal(2, flags + flag1); - Assert.Equal(3, flags + flag2); - Assert.Equal(2, flags + flag3); - Assert.Equal(7, flags + flag4); - - flags = 255; - Assert.Equal(255, flags + flag1); - Assert.Equal(255, flags + flag2); - Assert.Equal(255, flags + flag3); - Assert.Equal(255, flags + flag4); - } - - [Fact] - public void Substraction() - { - Flag8 flag1 = new Flag8(); - Flag8 flag2 = new Flag8(1); - Flag8 flag3 = new Flag8(2); - Flag8 flag4 = new Flag8(4, flag2); - - byte flags = 0; - Assert.Equal(0, flags - flag1); - Assert.Equal(0, flags - flag2); - Assert.Equal(0, flags - flag3); - Assert.Equal(0, flags - flag4); - - flags = 3; - Assert.Equal(3, flags - flag1); - Assert.Equal(2, flags - flag2); - Assert.Equal(1, flags - flag3); - Assert.Equal(3, flags - flag4); - - flags = 255; - Assert.Equal(255, flags - flag1); - Assert.Equal(250, flags - flag2); - Assert.Equal(253, flags - flag3); - Assert.Equal(251, flags - flag4); - } - - [Fact] - public void Includes() - { - Flag8 flag1 = new Flag8(); - Flag8 flag2 = new Flag8(1); - Flag8 flag3 = new Flag8(2); - Flag8 flag4 = new Flag8(4, flag2); - - byte flags = 0; - Assert.True(flags.Includes(flag1)); - Assert.False(flags.Includes(flag2)); - Assert.False(flags.Includes(flag3)); - Assert.False(flags.Includes(flag4)); - - flags = 6; - Assert.True(flags.Includes(flag1)); - Assert.False(flags.Includes(flag2)); - Assert.True(flags.Includes(flag3)); - Assert.False(flags.Includes(flag4)); - - flags = 7; - Assert.True(flags.Includes(flag1)); - Assert.True(flags.Includes(flag2)); - Assert.True(flags.Includes(flag3)); - Assert.True(flags.Includes(flag4)); - } - - [Fact] - public void Properties() - { - Flag8 flag1 = new Flag8(); - Flag8 flag2 = new Flag8(1); - Flag8 flag3 = new Flag8(2, flag1); - Flag8 flag4 = new Flag8(flag1); - Flag8 flag5 = new Flag8(4, flag2); - Flag8 flag6 = new Flag8(flag2); - Flag8 flag7 = new Flag8(flag4, flag6); - - Assert.True(flag1.IsNothing); - Assert.False(flag2.IsNothing); - Assert.False(flag3.IsNothing); - Assert.True(flag4.IsNothing); - Assert.False(flag5.IsNothing); - Assert.False(flag6.IsNothing); - Assert.False(flag7.IsNothing); - - Assert.False(flag1.IsConcrete); - Assert.True(flag2.IsConcrete); - Assert.True(flag3.IsConcrete); - Assert.False(flag4.IsConcrete); - Assert.True(flag5.IsConcrete); - Assert.False(flag6.IsConcrete); - Assert.False(flag7.IsConcrete); - - Assert.False(flag1.IsAbstract); - Assert.False(flag2.IsAbstract); - Assert.False(flag3.IsAbstract); - Assert.False(flag4.IsAbstract); - Assert.False(flag5.IsAbstract); - Assert.True(flag6.IsAbstract); - Assert.True(flag7.IsAbstract); - } - - [Fact] - public void PositiveEquality() - { - Flag8 flag1 = new Flag8(); - Flag8 flag2 = new Flag8(1); - Flag8 flag3 = new Flag8(2, flag2); - Flag8 flag4 = new Flag8(flag2); - - Assert.True(flag1.PositiveEquals(flag1)); - Assert.False(flag1.PositiveEquals(flag2)); - Assert.False(flag1.PositiveEquals(flag3)); - Assert.False(flag1.PositiveEquals(flag4)); - - Assert.False(flag2.PositiveEquals(flag1)); - Assert.True(flag2.PositiveEquals(flag2)); - Assert.False(flag2.PositiveEquals(flag3)); - Assert.True(flag2.PositiveEquals(flag4)); - - Assert.False(flag3.PositiveEquals(flag1)); - Assert.False(flag3.PositiveEquals(flag2)); - Assert.True(flag3.PositiveEquals(flag3)); - Assert.False(flag3.PositiveEquals(flag4)); - - Assert.False(flag4.PositiveEquals(flag1)); - Assert.True(flag4.PositiveEquals(flag2)); - Assert.False(flag4.PositiveEquals(flag3)); - Assert.True(flag4.PositiveEquals(flag4)); - } - - [Fact] - public void NegativeEquality() - { - Flag8 flag1 = new Flag8(); - Flag8 flag2 = new Flag8(1); - Flag8 flag3 = new Flag8(2, flag2); - Flag8 flag4 = new Flag8(flag2); - - Assert.True(flag1.NegativeEquals(flag1)); - Assert.False(flag1.NegativeEquals(flag2)); - Assert.False(flag1.NegativeEquals(flag3)); - Assert.True(flag1.NegativeEquals(flag4)); - - Assert.False(flag2.NegativeEquals(flag1)); - Assert.True(flag2.NegativeEquals(flag2)); - Assert.False(flag2.NegativeEquals(flag3)); - Assert.False(flag2.NegativeEquals(flag4)); - - Assert.False(flag3.NegativeEquals(flag1)); - Assert.False(flag3.NegativeEquals(flag2)); - Assert.True(flag3.NegativeEquals(flag3)); - Assert.False(flag3.NegativeEquals(flag4)); - - Assert.True(flag4.NegativeEquals(flag1)); - Assert.False(flag4.NegativeEquals(flag2)); - Assert.False(flag4.NegativeEquals(flag3)); - Assert.True(flag4.NegativeEquals(flag4)); - } - } -} \ No newline at end of file diff --git a/dotnet/Tests/FlagEnumTests.cs b/dotnet/Tests/FlagEnumTests.cs deleted file mode 100644 index 74319a5..0000000 --- a/dotnet/Tests/FlagEnumTests.cs +++ /dev/null @@ -1,215 +0,0 @@ -using Multiflag.BuiltInValueTypes; -using System.Reflection; - -namespace Tests -{ - public class FlagEnumTests - { - private enum TheEnum - { - A = 1, - B = 2, - C = 4, - - None = 0, - All = A | B | C - } - - private enum TheWrongEnum : ulong { Caca } - - [Fact] - public void Addition() - { - FlagEnum flag1 = new(); - FlagEnum flag2 = new(TheEnum.A); - FlagEnum flag3 = new(TheEnum.B); - FlagEnum flag4 = new(TheEnum.C, flag2); - - TheEnum flags = TheEnum.None; - Assert.Equal(TheEnum.None, flags + flag1); - Assert.Equal(TheEnum.A, flags + flag2); - Assert.Equal(TheEnum.B, flags + flag3); - Assert.Equal(TheEnum.C | TheEnum.A, flags + flag4); - - flags = TheEnum.B; - Assert.Equal(TheEnum.B, flags + flag1); - Assert.Equal(TheEnum.A | TheEnum.B, flags + flag2); - Assert.Equal(TheEnum.B, flags + flag3); - Assert.Equal(TheEnum.All, flags + flag4); - - flags = TheEnum.All; - Assert.Equal(TheEnum.All, flags + flag1); - Assert.Equal(TheEnum.All, flags + flag2); - Assert.Equal(TheEnum.All, flags + flag3); - Assert.Equal(TheEnum.All, flags + flag4); - } - - [Fact] - public void Substraction() - { - FlagEnum flag1 = new(); - FlagEnum flag2 = new(TheEnum.A); - FlagEnum flag3 = new(TheEnum.B); - FlagEnum flag4 = new(TheEnum.C, flag2); - - TheEnum flags = TheEnum.None; - Assert.Equal(TheEnum.None, flags - flag1); - Assert.Equal(TheEnum.None, flags - flag2); - Assert.Equal(TheEnum.None, flags - flag3); - Assert.Equal(TheEnum.None, flags - flag4); - - flags = TheEnum.A | TheEnum.B; - Assert.Equal(TheEnum.A | TheEnum.B, flags - flag1); - Assert.Equal(TheEnum.B, flags - flag2); - Assert.Equal(TheEnum.A, flags - flag3); - Assert.Equal(TheEnum.A | TheEnum.B, flags - flag4); - - flags = TheEnum.All; - Assert.Equal(TheEnum.All, flags - flag1); - Assert.Equal(TheEnum.B, flags - flag2); - Assert.Equal(TheEnum.A | TheEnum.C, flags - flag3); - Assert.Equal(TheEnum.A | TheEnum.B, flags - flag4); - } - - [Fact] - public void Includes() - { - FlagEnum flag1 = new(); - FlagEnum flag2 = new(TheEnum.A); - FlagEnum flag3 = new(TheEnum.B); - FlagEnum flag4 = new(TheEnum.C, flag2); - - TheEnum flags = TheEnum.None; - Assert.True(flags.Includes(flag1)); - Assert.False(flags.Includes(flag2)); - Assert.False(flags.Includes(flag3)); - Assert.False(flags.Includes(flag4)); - - flags = TheEnum.B | TheEnum.C; - Assert.True(flags.Includes(flag1)); - Assert.False(flags.Includes(flag2)); - Assert.True(flags.Includes(flag3)); - Assert.False(flags.Includes(flag4)); - - flags = TheEnum.All; - Assert.True(flags.Includes(flag1)); - Assert.True(flags.Includes(flag2)); - Assert.True(flags.Includes(flag3)); - Assert.True(flags.Includes(flag4)); - } - - [Fact] - public void Properties() - { - FlagEnum flag1 = new(); - FlagEnum flag2 = new(TheEnum.A); - FlagEnum flag3 = new(TheEnum.B, flag1); - FlagEnum flag4 = new(flag1); - FlagEnum flag5 = new(TheEnum.C, flag2); - FlagEnum flag6 = new(flag2); - FlagEnum flag7 = new(flag4, flag6); - - Assert.True(flag1.IsNothing); - Assert.False(flag2.IsNothing); - Assert.False(flag3.IsNothing); - Assert.True(flag4.IsNothing); - Assert.False(flag5.IsNothing); - Assert.False(flag6.IsNothing); - Assert.False(flag7.IsNothing); - - Assert.False(flag1.IsConcrete); - Assert.True(flag2.IsConcrete); - Assert.True(flag3.IsConcrete); - Assert.False(flag4.IsConcrete); - Assert.True(flag5.IsConcrete); - Assert.False(flag6.IsConcrete); - Assert.False(flag7.IsConcrete); - - Assert.False(flag1.IsAbstract); - Assert.False(flag2.IsAbstract); - Assert.False(flag3.IsAbstract); - Assert.False(flag4.IsAbstract); - Assert.False(flag5.IsAbstract); - Assert.True(flag6.IsAbstract); - Assert.True(flag7.IsAbstract); - } - - [Fact] - public void PositiveEquality() - { - FlagEnum flag1 = new(); - FlagEnum flag2 = new(TheEnum.A); - FlagEnum flag3 = new(TheEnum.B, flag2); - FlagEnum flag4 = new(flag2); - - Assert.True(flag1.PositiveEquals(flag1)); - Assert.False(flag1.PositiveEquals(flag2)); - Assert.False(flag1.PositiveEquals(flag3)); - Assert.False(flag1.PositiveEquals(flag4)); - - Assert.False(flag2.PositiveEquals(flag1)); - Assert.True(flag2.PositiveEquals(flag2)); - Assert.False(flag2.PositiveEquals(flag3)); - Assert.True(flag2.PositiveEquals(flag4)); - - Assert.False(flag3.PositiveEquals(flag1)); - Assert.False(flag3.PositiveEquals(flag2)); - Assert.True(flag3.PositiveEquals(flag3)); - Assert.False(flag3.PositiveEquals(flag4)); - - Assert.False(flag4.PositiveEquals(flag1)); - Assert.True(flag4.PositiveEquals(flag2)); - Assert.False(flag4.PositiveEquals(flag3)); - Assert.True(flag4.PositiveEquals(flag4)); - } - - [Fact] - public void NegativeEquality() - { - FlagEnum flag1 = new(); - FlagEnum flag2 = new(TheEnum.A); - FlagEnum flag3 = new(TheEnum.B, flag2); - FlagEnum flag4 = new(flag2); - - Assert.True(flag1.NegativeEquals(flag1)); - Assert.False(flag1.NegativeEquals(flag2)); - Assert.False(flag1.NegativeEquals(flag3)); - Assert.True(flag1.NegativeEquals(flag4)); - - Assert.False(flag2.NegativeEquals(flag1)); - Assert.True(flag2.NegativeEquals(flag2)); - Assert.False(flag2.NegativeEquals(flag3)); - Assert.False(flag2.NegativeEquals(flag4)); - - Assert.False(flag3.NegativeEquals(flag1)); - Assert.False(flag3.NegativeEquals(flag2)); - Assert.True(flag3.NegativeEquals(flag3)); - Assert.False(flag3.NegativeEquals(flag4)); - - Assert.True(flag4.NegativeEquals(flag1)); - Assert.False(flag4.NegativeEquals(flag2)); - Assert.False(flag4.NegativeEquals(flag3)); - Assert.True(flag4.NegativeEquals(flag4)); - } - - [Fact] - public void ThrowsWhenNotInt() - { - try - { - FlagEnum flag = new(TheWrongEnum.Caca); - } - catch (TargetInvocationException error) - { - if (error.InnerException is not InvalidEnumTypeException) - { - Assert.Fail($"Wrong exception type inside the TargetInvocationException (expected InvalidEnumTypeException, got {error.InnerException?.GetType()}"); - } - } - catch (Exception other) - { - Assert.Fail($"Wrong exception type thrown (expected TargetInvocationException, got {other.GetType()}"); - } - } - } -} \ No newline at end of file diff --git a/dotnet/Tests/FlagSetTests.cs b/dotnet/Tests/FlagSetTests.cs index 3c54141..4e44ec2 100644 --- a/dotnet/Tests/FlagSetTests.cs +++ b/dotnet/Tests/FlagSetTests.cs @@ -1,209 +1,22 @@ -using Multiflag.BuiltInValueTypes; +namespace Multiflag.Tests; -namespace Tests +public class FlagSetTests { - public class FlagSetTests + [Fact] + public void UseFlagFromOtherSetAsParent() { - private HashSet EmptySet => new HashSet { }; - private HashSet ASet => new HashSet { "A" }; - private HashSet BSet => new HashSet { "B" }; - private HashSet CSet => new HashSet { "C" }; - private HashSet ABSet => new HashSet { "A", "B" }; - private HashSet BCSet => new HashSet { "B", "C" }; - private HashSet ACSet => new HashSet { "A", "C" }; - private HashSet ABCSet => new HashSet { "A", "B", "C" }; + var flags = new U32BitflagSet(); + var flag = flags.Flag(1); - [Fact] - public void Addition() - { - FlagSet flag1 = new(); - FlagSet flag2 = new("A"); - FlagSet flag3 = new("B"); - FlagSet flag4 = new("C", flag2); - - HashSet flags = EmptySet; - Assert.Equal(EmptySet, flags + flag1); - flags = EmptySet; - Assert.Equal(ASet, flags + flag2); - flags = EmptySet; - Assert.Equal(BSet, flags + flag3); - flags = EmptySet; - Assert.Equal(ACSet, flags + flag4); - - flags = new HashSet { "B" }; - Assert.Equal(BSet, flags + flag1); - flags = new HashSet { "B" }; - Assert.Equal(ABSet, flags + flag2); - flags = new HashSet { "B" }; - Assert.Equal(BSet, flags + flag3); - flags = new HashSet { "B" }; - Assert.Equal(ABCSet, flags + flag4); - - flags = new HashSet { "A", "B", "C" }; - Assert.Equal(ABCSet, flags + flag1); - flags = new HashSet { "A", "B", "C" }; - Assert.Equal(ABCSet, flags + flag2); - flags = new HashSet { "A", "B", "C" }; - Assert.Equal(ABCSet, flags + flag3); - flags = new HashSet { "A", "B", "C" }; - Assert.Equal(ABCSet, flags + flag4); - } - - [Fact] - public void Substraction() - { - FlagSet flag1 = new(); - FlagSet flag2 = new("A"); - FlagSet flag3 = new("B"); - FlagSet flag4 = new("C", flag2); - - HashSet flags = EmptySet; - Assert.Equal(EmptySet, flags - flag1); - flags = EmptySet; - Assert.Equal(EmptySet, flags - flag2); - flags = EmptySet; - Assert.Equal(EmptySet, flags - flag3); - flags = EmptySet; - Assert.Equal(EmptySet, flags - flag4); - - flags = ABSet; - Assert.Equal(ABSet, flags - flag1); - flags = ABSet; - Assert.Equal(BSet, flags - flag2); - flags = ABSet; - Assert.Equal(ASet, flags - flag3); - flags = ABSet; - Assert.Equal(ABSet, flags - flag4); - - flags = ABCSet; - Assert.Equal(ABCSet, flags - flag1); - flags = ABCSet; - Assert.Equal(BSet, flags - flag2); - flags = ABCSet; - Assert.Equal(ACSet, flags - flag3); - flags = ABCSet; - Assert.Equal(ABSet, flags - flag4); - } - - [Fact] - public void Includes() - { - FlagSet flag1 = new(); - FlagSet flag2 = new(ASet); - FlagSet flag3 = new(BSet); - FlagSet flag4 = new("C", flag2); - - HashSet flags = EmptySet; - Assert.True(flags.Includes(flag1)); - Assert.False(flags.Includes(flag2)); - Assert.False(flags.Includes(flag3)); - Assert.False(flags.Includes(flag4)); - - flags = BCSet; - Assert.True(flags.Includes(flag1)); - Assert.False(flags.Includes(flag2)); - Assert.True(flags.Includes(flag3)); - Assert.False(flags.Includes(flag4)); - - flags = ABCSet; - Assert.True(flags.Includes(flag1)); - Assert.True(flags.Includes(flag2)); - Assert.True(flags.Includes(flag3)); - Assert.True(flags.Includes(flag4)); - } - - [Fact] - public void Properties() - { - FlagSet flag1 = new(); - FlagSet flag2 = new("A"); - FlagSet flag3 = new("B", flag1); - FlagSet flag4 = new(flag1); - FlagSet flag5 = new("C", flag2); - FlagSet flag6 = new(flag2); - FlagSet flag7 = new(flag4, flag6); - - Assert.True(flag1.IsNothing); - Assert.False(flag2.IsNothing); - Assert.False(flag3.IsNothing); - Assert.True(flag4.IsNothing); - Assert.False(flag5.IsNothing); - Assert.False(flag6.IsNothing); - Assert.False(flag7.IsNothing); - - Assert.False(flag1.IsConcrete); - Assert.True(flag2.IsConcrete); - Assert.True(flag3.IsConcrete); - Assert.False(flag4.IsConcrete); - Assert.True(flag5.IsConcrete); - Assert.False(flag6.IsConcrete); - Assert.False(flag7.IsConcrete); - - Assert.False(flag1.IsAbstract); - Assert.False(flag2.IsAbstract); - Assert.False(flag3.IsAbstract); - Assert.False(flag4.IsAbstract); - Assert.False(flag5.IsAbstract); - Assert.True(flag6.IsAbstract); - Assert.True(flag7.IsAbstract); - } - - [Fact] - public void PositiveEquality() - { - FlagSet flag1 = new(); - FlagSet flag2 = new("A"); - FlagSet flag3 = new("B", flag2); - FlagSet flag4 = new(flag2); - - Assert.True(flag1.PositiveEquals(flag1)); - Assert.False(flag1.PositiveEquals(flag2)); - Assert.False(flag1.PositiveEquals(flag3)); - Assert.False(flag1.PositiveEquals(flag4)); - - Assert.False(flag2.PositiveEquals(flag1)); - Assert.True(flag2.PositiveEquals(flag2)); - Assert.False(flag2.PositiveEquals(flag3)); - Assert.True(flag2.PositiveEquals(flag4)); - - Assert.False(flag3.PositiveEquals(flag1)); - Assert.False(flag3.PositiveEquals(flag2)); - Assert.True(flag3.PositiveEquals(flag3)); - Assert.False(flag3.PositiveEquals(flag4)); - - Assert.False(flag4.PositiveEquals(flag1)); - Assert.True(flag4.PositiveEquals(flag2)); - Assert.False(flag4.PositiveEquals(flag3)); - Assert.True(flag4.PositiveEquals(flag4)); - } - - [Fact] - public void NegativeEquality() - { - FlagSet flag1 = new(); - FlagSet flag2 = new("A"); - FlagSet flag3 = new("B", flag2); - FlagSet flag4 = new(flag2); - - Assert.True(flag1.NegativeEquals(flag1)); - Assert.False(flag1.NegativeEquals(flag2)); - Assert.False(flag1.NegativeEquals(flag3)); - Assert.True(flag1.NegativeEquals(flag4)); - - Assert.False(flag2.NegativeEquals(flag1)); - Assert.True(flag2.NegativeEquals(flag2)); - Assert.False(flag2.NegativeEquals(flag3)); - Assert.False(flag2.NegativeEquals(flag4)); - - Assert.False(flag3.NegativeEquals(flag1)); - Assert.False(flag3.NegativeEquals(flag2)); - Assert.True(flag3.NegativeEquals(flag3)); - Assert.False(flag3.NegativeEquals(flag4)); + var otherFlags = new U32BitflagSet(); + Assert.Throws(() => otherFlags.Flag(2, flag)); + } - Assert.True(flag4.NegativeEquals(flag1)); - Assert.False(flag4.NegativeEquals(flag2)); - Assert.False(flag4.NegativeEquals(flag3)); - Assert.True(flag4.NegativeEquals(flag4)); - } + [Fact] + public void UseSameValueTwice() + { + var flags = new U32BitflagSet(); + var flag = flags.Flag(1); + Assert.Throws(() => flags.Flag(1, flag)); } } \ No newline at end of file diff --git a/dotnet/Tests/FlagTests.cs b/dotnet/Tests/FlagTests.cs new file mode 100644 index 0000000..910c1dd --- /dev/null +++ b/dotnet/Tests/FlagTests.cs @@ -0,0 +1,141 @@ +namespace Multiflag.Tests; + +public class FlagTests +{ + [Fact] + public void AddTo() + { + Assert.Equal(1u, MyBitflags.Current.FlagA.AddTo(0)); + Assert.Equal(3u, MyBitflags.Current.FlagA.AddTo(2)); + Assert.Equal(7u, MyBitflags.Current.FlagA.AddTo(7)); + + Assert.Equal(2u, MyBitflags.Current.FlagB.AddTo(0)); + Assert.Equal(2u, MyBitflags.Current.FlagB.AddTo(2)); + Assert.Equal(7u, MyBitflags.Current.FlagB.AddTo(7)); + + Assert.Equal(3u, MyBitflags.Current.FlagC.AddTo(0)); + Assert.Equal(3u, MyBitflags.Current.FlagC.AddTo(2)); + Assert.Equal(7u, MyBitflags.Current.FlagC.AddTo(7)); + + Assert.Equal(7u, MyBitflags.Current.FlagD.AddTo(2)); + Assert.Equal(5u, MyBitflags.Current.FlagD.AddTo(0)); + Assert.Equal(7u, MyBitflags.Current.FlagD.AddTo(7)); + } + + [Fact] + public void Addition() + { + Assert.Equal(1u, 0 + MyBitflags.Current.FlagA); + Assert.Equal(3u, 2 + MyBitflags.Current.FlagA); + Assert.Equal(7u, 7 + MyBitflags.Current.FlagA); + + Assert.Equal(2u, 0 + MyBitflags.Current.FlagB); + Assert.Equal(2u, 2 + MyBitflags.Current.FlagB); + Assert.Equal(7u, 7 + MyBitflags.Current.FlagB); + + Assert.Equal(3u, 0 + MyBitflags.Current.FlagC); + Assert.Equal(3u, 2 + MyBitflags.Current.FlagC); + Assert.Equal(7u, 7 + MyBitflags.Current.FlagC); + + Assert.Equal(7u, 2 + MyBitflags.Current.FlagD); + Assert.Equal(5u, 0 + MyBitflags.Current.FlagD); + Assert.Equal(7u, 7 + MyBitflags.Current.FlagD); + } + + [Fact] + public void RemoveFrom() + { + Assert.Equal(0u, MyBitflags.Current.FlagA.RemoveFrom(0)); + Assert.Equal(2u, MyBitflags.Current.FlagA.RemoveFrom(2)); + Assert.Equal(2u, MyBitflags.Current.FlagA.RemoveFrom(7)); + + Assert.Equal(0u, MyBitflags.Current.FlagB.RemoveFrom(0)); + Assert.Equal(0u, MyBitflags.Current.FlagB.RemoveFrom(2)); + Assert.Equal(5u, MyBitflags.Current.FlagB.RemoveFrom(7)); + + Assert.Equal(0u, MyBitflags.Current.FlagC.RemoveFrom(0)); + Assert.Equal(2u, MyBitflags.Current.FlagC.RemoveFrom(2)); + Assert.Equal(7u, MyBitflags.Current.FlagC.RemoveFrom(7)); + + Assert.Equal(2u, MyBitflags.Current.FlagD.RemoveFrom(2)); + Assert.Equal(0u, MyBitflags.Current.FlagD.RemoveFrom(0)); + Assert.Equal(3u, MyBitflags.Current.FlagD.RemoveFrom(7)); + } + + [Fact] + public void Subtraction() + { + Assert.Equal(0u, 0 - MyBitflags.Current.FlagA); + Assert.Equal(2u, 2 - MyBitflags.Current.FlagA); + Assert.Equal(2u, 7 - MyBitflags.Current.FlagA); + + Assert.Equal(0u, 0 - MyBitflags.Current.FlagB); + Assert.Equal(0u, 2 - MyBitflags.Current.FlagB); + Assert.Equal(5u, 7 - MyBitflags.Current.FlagB); + + Assert.Equal(0u, 0 - MyBitflags.Current.FlagC); + Assert.Equal(2u, 2 - MyBitflags.Current.FlagC); + Assert.Equal(7u, 7 - MyBitflags.Current.FlagC); + + Assert.Equal(0u, 0 - MyBitflags.Current.FlagD); + Assert.Equal(2u, 2 - MyBitflags.Current.FlagD); + Assert.Equal(3u, 7 - MyBitflags.Current.FlagD); + } + + [Fact] + public void IsIn() + { + Assert.False(MyBitflags.Current.FlagA.IsIn(0)); + Assert.True(MyBitflags.Current.FlagA.IsIn(1)); + Assert.False(MyBitflags.Current.FlagA.IsIn(4)); + Assert.True(MyBitflags.Current.FlagA.IsIn(7)); + + Assert.False(MyBitflags.Current.FlagB.IsIn(0)); + Assert.True(MyBitflags.Current.FlagB.IsIn(2)); + Assert.False(MyBitflags.Current.FlagB.IsIn(4)); + Assert.True(MyBitflags.Current.FlagB.IsIn(7)); + + Assert.False(MyBitflags.Current.FlagC.IsIn(0)); + Assert.False(MyBitflags.Current.FlagC.IsIn(1)); + Assert.False(MyBitflags.Current.FlagC.IsIn(2)); + Assert.True(MyBitflags.Current.FlagC.IsIn(3)); + Assert.False(MyBitflags.Current.FlagC.IsIn(4)); + Assert.True(MyBitflags.Current.FlagC.IsIn(7)); + + Assert.False(MyBitflags.Current.FlagD.IsIn(0)); + Assert.False(MyBitflags.Current.FlagD.IsIn(1)); + Assert.False(MyBitflags.Current.FlagD.IsIn(4)); + Assert.True(MyBitflags.Current.FlagD.IsIn(5)); + Assert.False(MyBitflags.Current.FlagD.IsIn(6)); + Assert.True(MyBitflags.Current.FlagD.IsIn(7)); + } + + [Fact] + public void IsAbstract() + { + Assert.False(MyBitflags.Current.FlagA.IsAbstract); + Assert.False(MyBitflags.Current.FlagB.IsAbstract); + Assert.True(MyBitflags.Current.FlagC.IsAbstract); + Assert.False(MyBitflags.Current.FlagD.IsAbstract); + } + + private class MyBitflags : U32BitflagSet + { + private static MyBitflags? current; + + private MyBitflags() + { + this.FlagA = this.Flag(1); + this.FlagB = this.Flag(2); + this.FlagC = this.Flag(this.FlagA, this.FlagB); + this.FlagD = this.Flag(4, this.FlagA); + } + + public static MyBitflags Current => current ??= new MyBitflags(); + + public Flag FlagA { get; } + public Flag FlagB { get; } + public Flag FlagC { get; } + public Flag FlagD { get; } + } +} \ No newline at end of file diff --git a/dotnet/Tests/Tests.csproj b/dotnet/Tests/Tests.csproj index dfd9060..0a34995 100644 --- a/dotnet/Tests/Tests.csproj +++ b/dotnet/Tests/Tests.csproj @@ -1,33 +1,33 @@ - - net7.0 - enable - enable + + net9.0 + enable + enable + Multiflag.Tests + false + true + - false - true - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - + + + diff --git a/dotnet/Tests/U32BitflagSetTests.cs b/dotnet/Tests/U32BitflagSetTests.cs new file mode 100644 index 0000000..090de55 --- /dev/null +++ b/dotnet/Tests/U32BitflagSetTests.cs @@ -0,0 +1,152 @@ +namespace Multiflag.Tests; + +public class U32BitflagSetTests +{ + [Fact] + public void ValueNotAPowerOfTwo() + { + var flags = new U32BitflagSet(); + Assert.Throws(() => flags.Flag(0)); + Assert.Throws(() => flags.Flag(11)); + } + + [Fact] + public void Union() + { + var flags = new U32BitflagSet(); + + Assert.Equal(0u, flags.Union(0, 0)); + Assert.Equal(1u, flags.Union(1, 0)); + Assert.Equal(2u, flags.Union(0, 2)); + Assert.Equal(3u, flags.Union(1, 2)); + Assert.Equal(7u, flags.Union(3, 6)); + } + + [Fact] + public void Difference() { + var flags = new U32BitflagSet(); + + Assert.Equal(0u, flags.Difference(0, 0)); + Assert.Equal(1u, flags.Difference(1, 0)); + Assert.Equal(1u, flags.Difference(3, 6)); + Assert.Equal(4u, flags.Difference(6, 3)); + Assert.Equal(8u, flags.Difference(8, 17)); + } + + [Fact] + public void Intersection() + { + var flags = new U32BitflagSet(); + + Assert.Equal(0u, flags.Intersection(0, 0)); + Assert.Equal(0u, flags.Intersection(1, 0)); + Assert.Equal(0u, flags.Intersection(1, 2)); + Assert.Equal(1u, flags.Intersection(1, 3)); + Assert.Equal(1u, flags.Intersection(11, 5)); + Assert.Equal(3u, flags.Intersection(11, 7)); + } + + [Fact] + public void Iterate() + { + var flags = new U32BitflagSet(); + + Assert.Equal([], flags.Iterate(0)); + Assert.Equal([1], flags.Iterate(1)); + Assert.Equal([2], flags.Iterate(2)); + Assert.Equal([1, 2], flags.Iterate(3)); + Assert.Equal([1, 2, 8], flags.Iterate(11)); + Assert.Equal([4, 32, 64], flags.Iterate(100)); + } + + [Fact] + public void Minimum() + { + var flags = new U32BitflagSet(); + var flag1 = flags.Flag(1); + var flag2 = flags.Flag(2, flag1); + var flag4 = flags.Flag(4, flag1); + var flag8 = flags.Flag(8, flag4); + + Assert.Equal(0u, flags.Minimum(0)); + Assert.Equal(1u, flags.Minimum(1)); + Assert.Equal(0u, flags.Minimum(2)); + Assert.Equal(3u, flags.Minimum(3)); + Assert.Equal(3u, flags.Minimum(11)); + Assert.Equal(13u, flags.Minimum(13)); + Assert.Equal(1u, flags.Minimum(17)); + } + + [Fact] + public void Maximum() + { + var flags = new U32BitflagSet(); + var flag1 = flags.Flag(1); + var flag2 = flags.Flag(2, flag1); + var flag4 = flags.Flag(4, flag1); + var flag8 = flags.Flag(8, flag4); + + Assert.Equal(0u, flags.Maximum(0)); + Assert.Equal(1u, flags.Maximum(1)); + Assert.Equal(3u, flags.Maximum(2)); + Assert.Equal(3u, flags.Maximum(3)); + Assert.Equal(15u, flags.Maximum(11)); + Assert.Equal(13u, flags.Maximum(13)); + Assert.Equal(1u, flags.Maximum(17)); + } + + [Fact] + public void Add() + { + var flags = new U32BitflagSet(); + var flag2 = flags.Flag(2); + var flag4 = flags.Flag(4); + var flags2And4 = flags.Flag(flag2, flag4); + + Assert.Equal(3u, 1 + flag2); + Assert.Equal(5u, 1 + flag4); + Assert.Equal(7u, 1 + flags2And4); + } + + [Fact] + public void Remove() + { + var flags = new U32BitflagSet(); + var flag1 = flags.Flag(1); + var flag2 = flags.Flag(2); + var flag4 = flags.Flag(4, flag1); + + Assert.Equal(2u, 7 - flag1); + Assert.Equal(5u, 7 - flag2); + Assert.Equal(3u, 7 - flag4); + } + + [Fact] + public void IsIn() + { + var flags = new U32BitflagSet(); + var flag1 = flags.Flag(1); + var flag2 = flags.Flag(2); + var flag4 = flags.Flag(4, flag1); + + Assert.True(flag1.IsIn(1)); + Assert.True(flag2.IsIn(3)); + Assert.False(flag4.IsIn(4)); + Assert.True(flag4.IsIn(5)); + } + + [Fact] + public void IsAbstract() + { + var flags = new U32BitflagSet(); + var flag1 = flags.Flag(1); + var flag2 = flags.Flag(2); + var flags1And2 = flags.Flag(flag1, flag2); + var flag4 = flags.Flag(4, flags1And2); + + Assert.False(flag1.IsAbstract); + Assert.False(flag2.IsAbstract); + Assert.True(flags1And2.IsAbstract); + Assert.False(flag4.IsAbstract); + } +} \ No newline at end of file diff --git a/dotnet/Tests/U64BitflagSetTests.cs b/dotnet/Tests/U64BitflagSetTests.cs new file mode 100644 index 0000000..544e965 --- /dev/null +++ b/dotnet/Tests/U64BitflagSetTests.cs @@ -0,0 +1,152 @@ +namespace Multiflag.Tests; + +public class U64BitflagSetTests +{ + [Fact] + public void ValueNotAPowerOfTwo() + { + var flags = new U64BitflagSet(); + Assert.Throws(() => flags.Flag(0)); + Assert.Throws(() => flags.Flag(11)); + } + + [Fact] + public void Union() + { + var flags = new U64BitflagSet(); + + Assert.Equal(0u, flags.Union(0, 0)); + Assert.Equal(1u, flags.Union(1, 0)); + Assert.Equal(2u, flags.Union(0, 2)); + Assert.Equal(3u, flags.Union(1, 2)); + Assert.Equal(7u, flags.Union(3, 6)); + } + + [Fact] + public void Difference() { + var flags = new U64BitflagSet(); + + Assert.Equal(0u, flags.Difference(0, 0)); + Assert.Equal(1u, flags.Difference(1, 0)); + Assert.Equal(1u, flags.Difference(3, 6)); + Assert.Equal(4u, flags.Difference(6, 3)); + Assert.Equal(8u, flags.Difference(8, 17)); + } + + [Fact] + public void Intersection() + { + var flags = new U64BitflagSet(); + + Assert.Equal(0u, flags.Intersection(0, 0)); + Assert.Equal(0u, flags.Intersection(1, 0)); + Assert.Equal(0u, flags.Intersection(1, 2)); + Assert.Equal(1u, flags.Intersection(1, 3)); + Assert.Equal(1u, flags.Intersection(11, 5)); + Assert.Equal(3u, flags.Intersection(11, 7)); + } + + [Fact] + public void Iterate() + { + var flags = new U64BitflagSet(); + + Assert.Equal([], flags.Iterate(0)); + Assert.Equal([1], flags.Iterate(1)); + Assert.Equal([2], flags.Iterate(2)); + Assert.Equal([1, 2], flags.Iterate(3)); + Assert.Equal([1, 2, 8], flags.Iterate(11)); + Assert.Equal([4, 32, 64], flags.Iterate(100)); + } + + [Fact] + public void Minimum() + { + var flags = new U64BitflagSet(); + var flag1 = flags.Flag(1); + var flag2 = flags.Flag(2, flag1); + var flag4 = flags.Flag(4, flag1); + var flag8 = flags.Flag(8, flag4); + + Assert.Equal(0u, flags.Minimum(0)); + Assert.Equal(1u, flags.Minimum(1)); + Assert.Equal(0u, flags.Minimum(2)); + Assert.Equal(3u, flags.Minimum(3)); + Assert.Equal(3u, flags.Minimum(11)); + Assert.Equal(13u, flags.Minimum(13)); + Assert.Equal(1u, flags.Minimum(17)); + } + + [Fact] + public void Maximum() + { + var flags = new U64BitflagSet(); + var flag1 = flags.Flag(1); + var flag2 = flags.Flag(2, flag1); + var flag4 = flags.Flag(4, flag1); + var flag8 = flags.Flag(8, flag4); + + Assert.Equal(0u, flags.Maximum(0)); + Assert.Equal(1u, flags.Maximum(1)); + Assert.Equal(3u, flags.Maximum(2)); + Assert.Equal(3u, flags.Maximum(3)); + Assert.Equal(15u, flags.Maximum(11)); + Assert.Equal(13u, flags.Maximum(13)); + Assert.Equal(1u, flags.Maximum(17)); + } + + [Fact] + public void Add() + { + var flags = new U64BitflagSet(); + var flag2 = flags.Flag(2); + var flag4 = flags.Flag(4); + var flags2And4 = flags.Flag(flag2, flag4); + + Assert.Equal(3u, 1 + flag2); + Assert.Equal(5u, 1 + flag4); + Assert.Equal(7u, 1 + flags2And4); + } + + [Fact] + public void Remove() + { + var flags = new U64BitflagSet(); + var flag1 = flags.Flag(1); + var flag2 = flags.Flag(2); + var flag4 = flags.Flag(4, flag1); + + Assert.Equal(2u, 7 - flag1); + Assert.Equal(5u, 7 - flag2); + Assert.Equal(3u, 7 - flag4); + } + + [Fact] + public void IsIn() + { + var flags = new U64BitflagSet(); + var flag1 = flags.Flag(1); + var flag2 = flags.Flag(2); + var flag4 = flags.Flag(4, flag1); + + Assert.True(flag1.IsIn(1)); + Assert.True(flag2.IsIn(3)); + Assert.False(flag4.IsIn(4)); + Assert.True(flag4.IsIn(5)); + } + + [Fact] + public void IsAbstract() + { + var flags = new U64BitflagSet(); + var flag1 = flags.Flag(1); + var flag2 = flags.Flag(2); + var flags1And2 = flags.Flag(flag1, flag2); + var flag4 = flags.Flag(4, flags1And2); + + Assert.False(flag1.IsAbstract); + Assert.False(flag2.IsAbstract); + Assert.True(flags1And2.IsAbstract); + Assert.False(flag4.IsAbstract); + } +} \ No newline at end of file diff --git a/dotnet/global.json b/dotnet/global.json new file mode 100644 index 0000000..a27a2b8 --- /dev/null +++ b/dotnet/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "9.0.0", + "rollForward": "latestMajor", + "allowPrerelease": false + } +} \ No newline at end of file