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) [](https://www.nuget.org/packages/Multiflag)
-- [Multiflag JavaScript/TypeScript](node/README.md) [](https://https://www.npmjs.com/package/multiflag)
+- [Multiflag JavaScript/TypeScript](node/README.md) [](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