Repository: intel/rohd (dart)

Issue:
Make `LogicValue` comparable
### Motivation

Calling `sort` on a `List<LogicValue>` results in an error since they are not comparable.  We should implement functionality in `LogicValue` so that they can be naturally compared.

If the value is not valid, we should probably just throw an exception (like we do for other number-based operations).

### Desired solution

Make `LogicValue` implement [Comparable](https://api.dart.dev/stable/3.0.2/dart-core/Comparable-class.html)

Throw exception if not valid.

Consider width properly, since we need to use `int` vs `BigInt` depending on size.

### Alternatives considered

Current workaround is to sort based on `toInt()` or `toBigInt()`

### Additional details

_No response_

Pull request description:
<!-- Please make sure you check out the contribution guidelines before submitting a pull request! -->

## Description & Motivation

<!-- Description of changes, and motivation for adding them. -->
Calling `sort` on a `List<LogicValue>` results in an error since they are not comparable. We should implement functionality in `LogicValue` so that they can be naturally compared as per #371 

## Testing

<!-- Please describe how you tested your changes. -->

Added required test cases to file `test/logic_value_test.dart`

## Documentation

> Does the change require any updates to documentation? If so, where? Are they included?

<!-- Answer here. -->
Not included by me on `README.md`. Need list this addition functionality.

Patch:
diff --git a/lib/src/exceptions/logic/self_connecting_logic_exception.dart b/lib/src/exceptions/logic/self_connecting_logic_exception.dart
index dff550678..e40a404f7 100644
--- a/lib/src/exceptions/logic/self_connecting_logic_exception.dart
+++ b/lib/src/exceptions/logic/self_connecting_logic_exception.dart
@@ -1,13 +1,12 @@
-/// Copyright (C) 2023 Intel Corporation
-/// SPDX-License-Identifier: BSD-3-Clause
-///
-/// self_connecting_logic_exception.dart
-/// An exception that is thrown when there is a logical signal connecting to
-/// itself.
-///
-/// 2023 May 27
-/// Author: Sanchit Kumar <sanchit.dabgotra@gmail.com>
-///
+// Copyright (C) 2023 Intel Corporation
+// SPDX-License-Identifier: BSD-3-Clause
+//
+// self_connecting_logic_exception.dart
+// An exception that is thrown when there is a logical signal connecting to
+// itself.
+//
+// 2023 May 27
+// Author: Sanchit Kumar <sanchit.dabgotra@gmail.com>
 
 import 'package:rohd/rohd.dart';
 import 'package:rohd/src/exceptions/rohd_exception.dart';
diff --git a/lib/src/exceptions/logic_value/invalid_truncation_exception.dart b/lib/src/exceptions/logic_value/invalid_truncation_exception.dart
index cf42e2bee..43d9867f7 100644
--- a/lib/src/exceptions/logic_value/invalid_truncation_exception.dart
+++ b/lib/src/exceptions/logic_value/invalid_truncation_exception.dart
@@ -1,21 +1,20 @@
-/// Copyright (C) 2023 Intel Corporation
-/// SPDX-License-Identifier: BSD-3-Clause
-///
-/// invalid_truncation_exception.dart
-/// An exception that is thrown when invalid truncation takes place.
-///
-/// 2023 May 13
-/// Author: Sanchit Kumar <sanchit.dabgotra@gmail.com>
-///
+// Copyright (C) 2023 Intel Corporation
+// SPDX-License-Identifier: BSD-3-Clause
+//
+// invalid_truncation_exception.dart
+// An exception that is thrown when invalid truncation takes place.
+//
+// 2023 May 13
+// Author: Sanchit Kumar <sanchit.dabgotra@gmail.com>
 
 import 'package:rohd/rohd.dart';
 import 'package:rohd/src/exceptions/rohd_exception.dart';
 
-///An exception that is thrown when a [LogicValue] operation
+/// An exception that is thrown when a [LogicValue] operation
 /// couldn't be performed due invalid data truncation.
 class InvalidTruncationException extends RohdException {
-  ///Creates an exception when an invalid data truncation occurs
-  ///from present type to required type.
+  /// Creates an exception when an invalid data truncation occurs
+  /// from present type to required type.
   InvalidTruncationException(String message)
       : super("Logical operation couldn't be performed as $message");
 }
diff --git a/lib/src/exceptions/logic_value/invalid_value_operation_exception.dart b/lib/src/exceptions/logic_value/invalid_value_operation_exception.dart
new file mode 100644
index 000000000..ba94ea654
--- /dev/null
+++ b/lib/src/exceptions/logic_value/invalid_value_operation_exception.dart
@@ -0,0 +1,22 @@
+// Copyright (C) 2023 Intel Corporation
+// SPDX-License-Identifier: BSD-3-Clause
+//
+// invalid_value_operation_exception.dart
+// An exception that is thrown when a given operation cannot be performed on
+// invalid LogicValue
+//
+// 2023 June 4
+// Author: Sanchit Kumar <sanchit.dabgotra@gmail.com>
+
+import 'package:rohd/rohd.dart';
+import 'package:rohd/src/exceptions/rohd_exception.dart';
+
+/// An exception that is thrown when a given operation cannot be performed on
+/// invalid [LogicValue]
+class InvalidValueOperationException extends RohdException {
+  /// An exception that is thrown when a given operation [op] cannot be
+  /// performed on invalid input [a] i.e., [LogicValue.isValid] is
+  /// `false`.
+  InvalidValueOperationException(LogicValue a, String op)
+      : super('$op operation cannot be performed on invalid LogicValue $a');
+}
diff --git a/lib/src/exceptions/logic_value/logic_value_exceptions.dart b/lib/src/exceptions/logic_value/logic_value_exceptions.dart
index e2d4a37a9..4a00c1c18 100644
--- a/lib/src/exceptions/logic_value/logic_value_exceptions.dart
+++ b/lib/src/exceptions/logic_value/logic_value_exceptions.dart
@@ -2,4 +2,6 @@
 // SPDX-License-Identifier: BSD-3-Clause
 
 export 'invalid_truncation_exception.dart';
+export 'invalid_value_operation_exception.dart';
 export 'logic_value_construction_exception.dart';
+export 'value_width_mismatch_exception.dart';
diff --git a/lib/src/exceptions/logic_value/value_width_mismatch_exception.dart b/lib/src/exceptions/logic_value/value_width_mismatch_exception.dart
new file mode 100644
index 000000000..28945b40b
--- /dev/null
+++ b/lib/src/exceptions/logic_value/value_width_mismatch_exception.dart
@@ -0,0 +1,20 @@
+// Copyright (C) 2023 Intel Corporation
+// SPDX-License-Identifier: BSD-3-Clause
+//
+// value_width_mismatch_exception.dart
+// An exception that is thrown when LogicValue of different width are found.
+//
+// 2023 June 4
+// Author: Sanchit Kumar <sanchit.dabgotra@gmail.com>
+
+import 'package:rohd/rohd.dart';
+import 'package:rohd/src/exceptions/rohd_exception.dart';
+
+/// An exception that is thrown when [LogicValue]s of different width are found.
+class ValueWidthMismatchException extends RohdException {
+  /// Creates an exception when two [LogicValue] considered for the operation
+  /// are of different width.
+  ValueWidthMismatchException(LogicValue a, LogicValue b)
+      : super('Width Mismatch ${a.width} & ${b.width}: '
+            'LogicValue must be of same width');
+}
diff --git a/lib/src/values/logic_value.dart b/lib/src/values/logic_value.dart
index 7eff634ca..58c2d8b5c 100644
--- a/lib/src/values/logic_value.dart
+++ b/lib/src/values/logic_value.dart
@@ -19,7 +19,7 @@ typedef LogicValues = LogicValue;
 /// Each bit of [LogicValue] can be represented as a [LogicValue]
 /// of `0`, `1`, `x` (contention), or `z` (floating).
 @immutable
-abstract class LogicValue {
+abstract class LogicValue implements Comparable<LogicValue> {
   /// The number of bits in an int.
   // ignore: constant_identifier_names
   static const int _INT_BITS = 64;
@@ -565,6 +565,60 @@ abstract class LogicValue {
     }
   }
 
+  /// Compares this to `other`.
+  ///
+  /// Returns a negative number if `this` is less than `other`, zero if they are
+  /// equal, and a positive number if `this` is greater than `other`.
+  ///
+  /// It will throw an exception if `this` or [other] is not [isValid] or for
+  /// non-equal `LogicValue` [width].
+  @override
+  int compareTo(Object other) {
+    if (other is! LogicValue) {
+      throw Exception('Input must be of type LogicValue ');
+    }
+
+    if (!isValid) {
+      throw InvalidValueOperationException(this, 'Comparison');
+    }
+
+    if (!other.isValid) {
+      throw InvalidValueOperationException(other, 'Comparison');
+    }
+
+    if (other.width != width) {
+      throw ValueWidthMismatchException(this, other);
+    }
+
+    if (width > _INT_BITS || other.width > _INT_BITS) {
+      final a = toBigInt();
+      final b = other.toBigInt();
+      final valueZero = BigInt.zero;
+
+      if ((a < valueZero && b < valueZero) ||
+          (a >= valueZero && b >= valueZero)) {
+        return a.compareTo(b);
+      } else if (a < valueZero && b >= valueZero) {
+        return 1;
+      } else {
+        return -1;
+      }
+    } else {
+      final a = toInt();
+      final b = other.toInt();
+      const valueZero = 0;
+
+      if ((a < valueZero && b < valueZero) ||
+          (a >= valueZero && b >= valueZero)) {
+        return a.compareTo(b);
+      } else if (a < valueZero && b >= valueZero) {
+        return 1;
+      } else {
+        return -1;
+      }
+    }
+  }
+
   String _bitString() {
     if (width != 1) {
       throw Exception(


Test patch:
diff --git a/test/logic_value_test.dart b/test/logic_value_test.dart
index 866f236f5..dd415d3a7 100644
--- a/test/logic_value_test.dart
+++ b/test/logic_value_test.dart
@@ -1252,4 +1252,162 @@ void main() {
       }
     });
   });
+  group('Comparable LogicValue', () {
+    test('positive - int', () {
+      final a = LogicValue.ofInt(3, 8);
+      final b = LogicValue.ofInt(0, 8);
+      final c = LogicValue.ofInt(1, 8);
+      final d = LogicValue.ofInt(2, 8);
+      final e = LogicValue.ofInt(23, 8);
+      final f = LogicValue.ofInt(44, 8);
+      final g = LogicValue.ofInt(2, 8);
+      final h = LogicValue.ofInt(9, 8);
+
+      final values = <LogicValue>[a, b, c, d, e, f, g, h];
+
+      final expected = List<int>.filled(values.length, 0);
+
+      for (var i = 0; i < values.length; i++) {
+        expected[i] = values[i].toInt();
+      }
+
+      values.sort();
+      expected.sort();
+      for (var i = 0; i < values.length; i++) {
+        expect(values[i].toInt(), expected[i]);
+      }
+    });
+    test('unsigned values - int 64 bits', () {
+      final a = LogicValue.ofInt(3, 64);
+      final b = LogicValue.ofInt(0, 64);
+      final c = LogicValue.ofInt(1, 64);
+      final d = LogicValue.ofInt(2, 64);
+      final e = LogicValue.ofInt(23, 64);
+      final f = LogicValue.ofInt(-127, 64);
+      final g = LogicValue.ofInt(-128, 64);
+      final h = LogicValue.ofInt(-1, 64);
+
+      final values = <LogicValue>[a, b, c, d, e, f, g, h];
+
+      final expected = List<BigInt>.filled(values.length, BigInt.zero);
+
+      for (var i = 0; i < values.length; i++) {
+        expected[i] =
+            BigInt.from(values[i].toInt()).toUnsigned(values[i].width);
+      }
+
+      values.sort();
+      expected.sort();
+      for (var i = 0; i < values.length; i++) {
+        expect(BigInt.from(values[i].toInt()).toUnsigned(values[i].width),
+            expected[i]);
+      }
+    });
+    test('unsigned values - int 8 bits', () {
+      final a = LogicValue.ofInt(3, 8);
+      final b = LogicValue.ofInt(0, 8);
+      final c = LogicValue.ofInt(1, 8);
+      final d = LogicValue.ofInt(2, 8);
+      final e = LogicValue.ofInt(23, 8);
+      final f = LogicValue.ofInt(-127, 8);
+      final g = LogicValue.ofInt(-128, 8);
+      final h = LogicValue.ofInt(-1, 8);
+
+      final values = <LogicValue>[a, b, c, d, e, f, g, h];
+
+      final expected = List<BigInt>.filled(values.length, BigInt.zero);
+
+      for (var i = 0; i < values.length; i++) {
+        expected[i] =
+            BigInt.from(values[i].toInt()).toUnsigned(values[i].width);
+      }
+
+      values.sort();
+      expected.sort();
+
+      for (var i = 0; i < values.length; i++) {
+        expect(BigInt.from(values[i].toInt()).toUnsigned(values[i].width),
+            expected[i]);
+      }
+    });
+    test('unsigned  BigInt & int ', () {
+      final a = LogicValue.ofBigInt(BigInt.parse('3'), 64);
+      final b = LogicValue.ofBigInt(BigInt.zero, 64);
+      final c = LogicValue.ofBigInt(BigInt.from(-1), 64);
+      final d = LogicValue.ofBigInt(BigInt.one, 64);
+      final e = LogicValue.ofInt(-4611686018427387903, 64);
+      final f = LogicValue.ofInt(-9223372036854775808, 64);
+      final g = LogicValue.ofInt(-9223372036854775807, 64);
+      final h = LogicValue.ofInt(-1, 64);
+
+      final values = <LogicValue>[a, b, c, d, e, f, g, h];
+
+      final expected = List<BigInt>.filled(values.length, BigInt.zero);
+      for (var i = 0; i < values.length; i++) {
+        expected[i] =
+            BigInt.from(values[i].toInt()).toUnsigned(values[i].width);
+      }
+
+      values.sort();
+      expected.sort();
+      for (var i = 0; i < values.length; i++) {
+        expect(BigInt.from(values[i].toInt()).toUnsigned(values[i].width),
+            expected[i]);
+      }
+    });
+    test('unsigned BigInt', () {
+      final a = LogicValue.ofBigInt(BigInt.parse('3'), 128);
+      final b = LogicValue.ofBigInt(BigInt.zero, 128);
+      final c = LogicValue.ofBigInt(BigInt.from(-1), 128);
+      final d = LogicValue.ofBigInt(BigInt.one, 128);
+      final e = LogicValue.ofBigInt(
+          BigInt.parse('340282366920938463463374607431768211455'), 128);
+      final f = LogicValue.ofBigInt(
+          BigInt.parse('-170141183460469231731687303715884105727'), 128);
+      final g = LogicValue.ofBigInt(
+          BigInt.parse('-170141183460469231731687303715884105728'), 128);
+      final h = LogicValue.ofBigInt(BigInt.one, 128);
+
+      final values = <LogicValue>[a, b, c, d, e, f, g, h];
+
+      final expected = List<BigInt>.filled(values.length, BigInt.zero);
+
+      for (var i = 0; i < values.length; i++) {
+        expected[i] = values[i].toBigInt().toUnsigned(values[i].width);
+      }
+
+      values.sort();
+      expected.sort();
+      for (var i = 0; i < values.length; i++) {
+        expect(values[i].toBigInt().toUnsigned(values[i].width), expected[i]);
+      }
+    });
+    test('Exceptions', () {
+      final a64 = LogicValue.ofBigInt(BigInt.parse('3'), 64);
+      final a128 = LogicValue.ofBigInt(BigInt.parse('3'), 128);
+      final b64 = LogicValue.ofBigInt(BigInt.zero, 64);
+      final c64 = LogicValue.ofBigInt(BigInt.from(-1), 64);
+      final d64 = LogicValue.ofBigInt(BigInt.one, 64);
+      final e64 = LogicValue.ofInt(23, 64);
+      final e8 = LogicValue.ofInt(23, 8);
+      final f64 = LogicValue.ofInt(-9223372036854775808, 64);
+      final g64 = LogicValue.ofInt(-9223372036854775807, 64);
+      final h64 = LogicValue.ofInt(-1, 64);
+      final invalidLogicX = LogicValue.filled(8, LogicValue.x);
+      final invalidLogicZ = LogicValue.filled(8, LogicValue.z);
+
+      <LogicValue>[a64, b64, c64, d64, e64, f64, g64, h64].sort();
+
+      final excBigIntWidth = <LogicValue>[a64, b64, a128];
+      final excIntWidth = <LogicValue>[e64, e8, f64];
+
+      expect(excBigIntWidth.sort, throwsA(isA<ValueWidthMismatchException>()));
+      expect(excIntWidth.sort, throwsA(isA<ValueWidthMismatchException>()));
+
+      expect(<LogicValue>[e8, invalidLogicX].sort,
+          throwsA(isA<InvalidValueOperationException>()));
+      expect(<LogicValue>[e8, invalidLogicZ].sort,
+          throwsA(isA<InvalidValueOperationException>()));
+    });
+  });
 }