diff --git a/Tests/ByteTest.php b/Tests/ByteTest.php new file mode 100644 index 0000000..38f1306 --- /dev/null +++ b/Tests/ByteTest.php @@ -0,0 +1,198 @@ +expectException(\InvalidArgumentException::class); + new Byte(300); // Out of range value + } + + /** + * Test that the constructor correctly assigns the value. + */ + public function testConstructorAssignsValidValue() + { + $byte = new Byte(100); + $this->assertEquals(100, $byte->getValue()); + } + + /** + * Test bitwise AND operation. + */ + public function testAndOperation() + { + $byte1 = new Byte(170); // 10101010 + $byte2 = new Byte(85); // 01010101 + $result = $byte1->and($byte2); + + $this->assertEquals(0, $result->getValue()); // 00000000 + } + + /** + * Test bitwise OR operation. + */ + public function testOrOperation() + { + $byte1 = new Byte(170); // 10101010 + $byte2 = new Byte(85); // 01010101 + $result = $byte1->or($byte2); + + $this->assertEquals(255, $result->getValue()); // 11111111 + } + + /** + * Test bitwise XOR operation. + */ + public function testXorOperation() + { + $byte1 = new Byte(170); // 10101010 + $byte2 = new Byte(85); // 01010101 + $result = $byte1->xor($byte2); + + $this->assertEquals(255, $result->getValue()); // 11111111 + } + + /** + * Test bitwise NOT operation. + */ + public function testNotOperation() + { + $byte = new Byte(170); // 10101010 + $result = $byte->not(); + + $this->assertEquals(85, $result->getValue()); // 01010101 + } + + /** + * Test left bit shifting. + */ + public function testShiftLeftOperation() + { + $byte = new Byte(15); // 00001111 + $result = $byte->shiftLeft(2); + + $this->assertEquals(60, $result->getValue()); // 00111100 + } + + /** + * Test right bit shifting. + */ + public function testShiftRightOperation() + { + $byte = new Byte(240); // 11110000 + $result = $byte->shiftRight(2); + + $this->assertEquals(60, $result->getValue()); // 00111100 + } + + /** + * Test equality comparison between two bytes. + */ + public function testEquals() + { + $byte1 = new Byte(100); + $byte2 = new Byte(100); + + $this->assertTrue($byte1->equals($byte2)); + } + + /** + * Test greater than comparison. + */ + public function testIsGreaterThan() + { + $byte1 = new Byte(200); + $byte2 = new Byte(100); + + $this->assertTrue($byte1->isGreaterThan($byte2)); + } + + /** + * Test less than comparison. + */ + public function testIsLessThan() + { + $byte1 = new Byte(50); + $byte2 = new Byte(100); + + $this->assertTrue($byte1->isLessThan($byte2)); + } + + /** + * Test converting byte to binary string. + */ + public function testToBinary() + { + $byte = new Byte(170); // 10101010 + $this->assertEquals('10101010', $byte->toBinary()); + } + + /** + * Test converting byte to hexadecimal string. + */ + public function testToHex() + { + $byte = new Byte(170); // 0xAA + $this->assertEquals('AA', $byte->toHex()); + } + + /** + * Test creating a byte from binary string. + */ + public function testFromBinary() + { + $byte = Byte::fromBinary('10101010'); + $this->assertEquals(170, $byte->getValue()); + } + + /** + * Test creating a byte from hexadecimal string. + */ + public function testFromHex() + { + $byte = Byte::fromHex('AA'); + $this->assertEquals(170, $byte->getValue()); + } + + /** + * Test adding a value to the byte and wrapping around at 255. + */ + public function testAddWithOverflow() + { + $byte = new Byte(250); + $result = $byte->add(10); + + $this->assertEquals(4, $result->getValue()); // 250 + 10 = 260, wrapped to 4 + } + + /** + * Test subtracting a value from the byte and wrapping around at 0. + */ + public function testSubtractWithUnderflow() + { + $byte = new Byte(5); + $result = $byte->subtract(10); + + $this->assertEquals(251, $result->getValue()); // 5 - 10 = -5, wrapped to 251 + } + + /** + * Test string representation of the byte. + */ + public function testToString() + { + $byte = new Byte(100); + $this->assertEquals('100', (string) $byte); + } +} diff --git a/Tests/CharTest.php b/Tests/CharTest.php new file mode 100644 index 0000000..a1ac546 --- /dev/null +++ b/Tests/CharTest.php @@ -0,0 +1,148 @@ +expectException(\InvalidArgumentException::class); + new Char('AB'); // Invalid, more than 1 character + } + + /** + * Test that the constructor correctly assigns a single character. + */ + public function testConstructorAssignsValidValue() + { + $char = new Char('A'); + $this->assertEquals('A', $char->getValue()); + } + + /** + * Test converting a character to uppercase. + */ + public function testToUpperCase() + { + $char = new Char('a'); + $upperChar = $char->toUpperCase(); + + $this->assertEquals('A', $upperChar->getValue()); + } + + /** + * Test converting a character to lowercase. + */ + public function testToLowerCase() + { + $char = new Char('A'); + $lowerChar = $char->toLowerCase(); + + $this->assertEquals('a', $lowerChar->getValue()); + } + + /** + * Test if a character is a letter. + */ + public function testIsLetter() + { + $char = new Char('A'); + $this->assertTrue($char->isLetter()); + + $char = new Char('1'); + $this->assertFalse($char->isLetter()); + } + + /** + * Test if a character is a digit. + */ + public function testIsDigit() + { + $char = new Char('1'); + $this->assertTrue($char->isDigit()); + + $char = new Char('A'); + $this->assertFalse($char->isDigit()); + } + + /** + * Test if a character is uppercase. + */ + public function testIsUpperCase() + { + $char = new Char('A'); + $this->assertTrue($char->isUpperCase()); + + $char = new Char('a'); + $this->assertFalse($char->isUpperCase()); + } + + /** + * Test if a character is lowercase. + */ + public function testIsLowerCase() + { + $char = new Char('a'); + $this->assertTrue($char->isLowerCase()); + + $char = new Char('A'); + $this->assertFalse($char->isLowerCase()); + } + + /** + * Test the equality of two Char objects. + */ + public function testEquals() + { + $char1 = new Char('A'); + $char2 = new Char('A'); + $char3 = new Char('B'); + + $this->assertTrue($char1->equals($char2)); + $this->assertFalse($char1->equals($char3)); + } + + /** + * Test converting a character to its ASCII code. + */ + public function testToAscii() + { + $char = new Char('A'); + $this->assertEquals(65, $char->toAscii()); + } + + /** + * Test creating a Char from an ASCII code. + */ + public function testFromAscii() + { + $char = Char::fromAscii(65); // ASCII for 'A' + $this->assertEquals('A', $char->getValue()); + } + + /** + * Test that an exception is thrown for an invalid ASCII code. + */ + public function testFromAsciiThrowsExceptionOnInvalidValue() + { + $this->expectException(\InvalidArgumentException::class); + Char::fromAscii(300); // Invalid, out of ASCII range + } + + /** + * Test converting a Char object to a string. + */ + public function testToString() + { + $char = new Char('A'); + $this->assertEquals('A', (string) $char); + } +} diff --git a/Tests/DictionaryTest.php b/Tests/DictionaryTest.php index 3647060..ff41ac2 100644 --- a/Tests/DictionaryTest.php +++ b/Tests/DictionaryTest.php @@ -1,6 +1,11 @@ assertInstanceOf(Union::class, $union); + } + + /** + * Test that setting a valid value works. + */ + public function testSetValueWithValidType() + { + $union = new Union(['int', 'string']); + $union->setValue(123); + $this->assertEquals(123, $union->getValue()); + + $union->setValue('hello'); + $this->assertEquals('hello', $union->getValue()); + } + + /** + * Test that setting an invalid type throws an exception. + */ + public function testSetValueWithInvalidTypeThrowsException() + { + $union = new Union(['int', 'string']); + + $this->expectException(InvalidArgumentException::class); + $union->setValue(1.5); // float is not allowed + } + + /** + * Test that object type validation works. + */ + public function testSetValueWithObjectType() + { + $union = new Union([DateTime::class]); + $date = new DateTime(); + + $union->setValue($date); + $this->assertEquals($date, $union->getValue()); + } + + /** + * Test that dynamically adding allowed types works. + */ + public function testAddAllowedType() + { + $union = new Union(['int']); + $union->addAllowedType('float'); + + $union->setValue(1.5); + $this->assertEquals(1.5, $union->getValue()); + } + + /** + * Test that isType works for scalar types. + */ + public function testIsTypeWithScalarTypes() + { + $union = new Union(['int', 'string']); + $union->setValue(123); + + $this->assertTrue($union->isType('int')); + $this->assertFalse($union->isType('string')); + } + + /** + * Test that isType works for object types. + */ + public function testIsTypeWithObjectTypes() + { + $union = new Union([DateTime::class]); + $date = new DateTime(); + + $union->setValue($date); + $this->assertTrue($union->isType(DateTime::class)); + } + + /** + * Test that validateType correctly handles invalid object types. + */ + public function testValidateTypeThrowsExceptionForInvalidObjectType() + { + $union = new Union([DateTime::class]); + + $this->expectException(InvalidArgumentException::class); + $union->setValue(new stdClass()); // stdClass is not allowed + } +} diff --git a/composer.json b/composer.json index c97f98a..ac6661e 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ ], "require": { "php": "^8.2", - "ext-bcmath": "*" + "ext-bcmath": "*", + "ext-ctype": "*" }, "require-dev": { "phpunit/phpunit": "^11.4.2" diff --git a/composer.lock b/composer.lock index 0c3e29f..7182f09 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "532c2e899eecafecc955cd9ed173b9d2", + "content-hash": "271ccda664646033d9a3393b06caecfc", "packages": [], "packages-dev": [ { @@ -69,16 +69,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.3.0", + "version": "v5.3.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a" + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3abf7425cd284141dc5d8d14a9ee444de3345d1a", - "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", "shasum": "" }, "require": { @@ -121,9 +121,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" }, - "time": "2024-09-29T13:56:26+00:00" + "time": "2024-10-08T18:51:32+00:00" }, { "name": "phar-io/manifest", @@ -245,35 +245,35 @@ }, { "name": "phpunit/php-code-coverage", - "version": "11.0.6", + "version": "11.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "ebdffc9e09585dafa71b9bffcdb0a229d4704c45" + "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ebdffc9e09585dafa71b9bffcdb0a229d4704c45", - "reference": "ebdffc9e09585dafa71b9bffcdb0a229d4704c45", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f7f08030e8811582cc459871d28d6f5a1a4d35ca", + "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^5.1.0", + "nikic/php-parser": "^5.3.1", "php": ">=8.2", - "phpunit/php-file-iterator": "^5.0.1", + "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-text-template": "^4.0.1", "sebastian/code-unit-reverse-lookup": "^4.0.1", "sebastian/complexity": "^4.0.1", "sebastian/environment": "^7.2.0", "sebastian/lines-of-code": "^3.0.1", - "sebastian/version": "^5.0.1", + "sebastian/version": "^5.0.2", "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.4.1" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -311,7 +311,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.6" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.7" }, "funding": [ { @@ -319,7 +319,7 @@ "type": "github" } ], - "time": "2024-08-22T04:37:56+00:00" + "time": "2024-10-09T06:21:38+00:00" }, { "name": "phpunit/php-file-iterator", @@ -568,16 +568,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.4.1", + "version": "11.4.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "7875627f15f4da7e7f0823d1f323f7295a77334e" + "reference": "1863643c3f04ad03dcb9c6996c294784cdda4805" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7875627f15f4da7e7f0823d1f323f7295a77334e", - "reference": "7875627f15f4da7e7f0823d1f323f7295a77334e", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1863643c3f04ad03dcb9c6996c294784cdda4805", + "reference": "1863643c3f04ad03dcb9c6996c294784cdda4805", "shasum": "" }, "require": { @@ -591,21 +591,21 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.6", + "phpunit/php-code-coverage": "^11.0.7", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", "sebastian/cli-parser": "^3.0.2", "sebastian/code-unit": "^3.0.1", - "sebastian/comparator": "^6.1.0", + "sebastian/comparator": "^6.1.1", "sebastian/diff": "^6.0.2", "sebastian/environment": "^7.2.0", "sebastian/exporter": "^6.1.3", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", "sebastian/type": "^5.1.0", - "sebastian/version": "^5.0.1" + "sebastian/version": "^5.0.2" }, "suggest": { "ext-soap": "To be able to generate mocks based on WSDL files" @@ -648,7 +648,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.4.1" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.4.2" }, "funding": [ { @@ -664,7 +664,7 @@ "type": "tidelift" } ], - "time": "2024-10-08T15:38:37+00:00" + "time": "2024-10-19T13:05:19+00:00" }, { "name": "sebastian/cli-parser", @@ -838,16 +838,16 @@ }, { "name": "sebastian/comparator", - "version": "6.1.0", + "version": "6.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa37b9e2ca618cb051d71b60120952ee8ca8b03d" + "reference": "5ef523a49ae7a302b87b2102b72b1eda8918d686" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa37b9e2ca618cb051d71b60120952ee8ca8b03d", - "reference": "fa37b9e2ca618cb051d71b60120952ee8ca8b03d", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5ef523a49ae7a302b87b2102b72b1eda8918d686", + "reference": "5ef523a49ae7a302b87b2102b72b1eda8918d686", "shasum": "" }, "require": { @@ -903,7 +903,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.1.0" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.1.1" }, "funding": [ { @@ -911,7 +911,7 @@ "type": "github" } ], - "time": "2024-09-11T15:42:56+00:00" + "time": "2024-10-18T15:00:48+00:00" }, { "name": "sebastian/complexity", @@ -1537,16 +1537,16 @@ }, { "name": "sebastian/version", - "version": "5.0.1", + "version": "5.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "45c9debb7d039ce9b97de2f749c2cf5832a06ac4" + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/45c9debb7d039ce9b97de2f749c2cf5832a06ac4", - "reference": "45c9debb7d039ce9b97de2f749c2cf5832a06ac4", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", "shasum": "" }, "require": { @@ -1579,7 +1579,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/version/issues", "security": "https://github.com/sebastianbergmann/version/security/policy", - "source": "https://github.com/sebastianbergmann/version/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" }, "funding": [ { @@ -1587,7 +1587,7 @@ "type": "github" } ], - "time": "2024-07-03T05:13:08+00:00" + "time": "2024-10-09T05:16:32+00:00" }, { "name": "theseer/tokenizer", @@ -1647,7 +1647,8 @@ "prefer-lowest": false, "platform": { "php": "^8.2", - "ext-bcmath": "*" + "ext-bcmath": "*", + "ext-ctype": "*" }, "platform-dev": [], "plugin-api-version": "2.6.0" diff --git a/src/Composite/Dictionary.php b/src/Composite/Dictionary.php index e5391a0..7229629 100644 --- a/src/Composite/Dictionary.php +++ b/src/Composite/Dictionary.php @@ -1,4 +1,5 @@ 'integer', + 'float' => 'double', + 'bool' => 'boolean', + ]; + + /** + * Create a new Union instance with allowed types. + * * @param array $allowedTypes + * @return void */ public function __construct(array $allowedTypes) { @@ -25,8 +43,12 @@ public function __construct(array $allowedTypes) } /** + * Set the value of the union, validating the type. + * * @param mixed $value * @return void + * + * @throws InvalidArgumentException */ public function setValue(mixed $value): void { @@ -35,6 +57,8 @@ public function setValue(mixed $value): void } /** + * Get the current value of the union. + * * @return mixed */ public function getValue(): mixed @@ -43,15 +67,65 @@ public function getValue(): mixed } /** + * Validate the type of the value against allowed types. + * * @param mixed $value * @return void + * + * @throws InvalidArgumentException */ private function validateType(mixed $value): void { $type = gettype($value); + // If the type is in the type map, convert to the shorthand notation + $shorthandType = array_search($type, self::$typeMap, true); + if ($shorthandType) { + $type = $shorthandType; + } + + // If it's a class, validate using 'instanceof' for better OOP support. + foreach ($this->allowedTypes as $allowedType) { + if (class_exists($allowedType) && $value instanceof $allowedType) { + return; + } + } + + // Check against the allowed types array. + if (!in_array($type, $this->allowedTypes, true)) { + throw new InvalidArgumentException( + "Invalid type: $type. Allowed types: " . implode(', ', $this->allowedTypes) + ); + } + } + + /** + * Determine if the current value is of a specific type. + * + * @param string $type + * @return bool + */ + public function isType(string $type): bool + { + $actualType = gettype($this->value); + + // Map to shorthand if applicable + $shorthandType = array_search($actualType, self::$typeMap, true); + $actualType = $shorthandType ?: $actualType; + + return $actualType === $type || $this->value instanceof $type; + } + + /** + * Add a new type to the allowed types of the union. + * + * @param string $type + * @return void + */ + public function addAllowedType(string $type): void + { if (!in_array($type, $this->allowedTypes, true)) { - throw new InvalidArgumentException("Invalid type: $type. Allowed types: " . implode(', ', $this->allowedTypes)); + $this->allowedTypes[] = $type; } } } diff --git a/src/Scalar/Byte.php b/src/Scalar/Byte.php index 6bf09c7..880eae1 100644 --- a/src/Scalar/Byte.php +++ b/src/Scalar/Byte.php @@ -1,18 +1,226 @@ setValue($value); + } + + /** + * Set the byte value ensuring it is between 0 and 255. + * + * @param int $value + * @return void + * + * @throws \InvalidArgumentException + */ + private function setValue(int $value): void + { if ($value < 0 || $value > 255) { - throw new \InvalidArgumentException("Byte value must be between 0 and 255."); + throw new \InvalidArgumentException('Byte value must be between 0 and 255.'); } + $this->value = $value; } - public function getValue(): int { + /** + * Get the byte value. + * + * @return int + */ + public function getValue(): int + { return $this->value; } + + /** + * Perform a bitwise AND operation on this byte and another. + * + * @param Byte $byte + * @return Byte + */ + public function and(Byte $byte): Byte + { + return new self($this->value & $byte->getValue()); + } + + /** + * Perform a bitwise OR operation on this byte and another. + * + * @param Byte $byte + * @return Byte + */ + public function or(Byte $byte): Byte + { + return new self($this->value | $byte->getValue()); + } + + /** + * Perform a bitwise XOR operation on this byte and another. + * + * @param Byte $byte + * @return Byte + */ + public function xor(Byte $byte): Byte + { + return new self($this->value ^ $byte->getValue()); + } + + /** + * Perform a bitwise NOT operation on this byte. + * + * @return Byte + */ + public function not(): Byte + { + return new self(~$this->value & 0xFF); // Ensures the result stays within 8 bits + } + + /** + * Shift the bits of this byte to the left. + * + * @param int $positions + * @return Byte + */ + public function shiftLeft(int $positions): Byte + { + return new self(($this->value << $positions) & 0xFF); // Prevent overflow + } + + /** + * Shift the bits of this byte to the right. + * + * @param int $positions + * @return Byte + */ + public function shiftRight(int $positions): Byte + { + return new self($this->value >> $positions); + } + + /** + * Determine if this byte is equal to another byte. + * + * @param Byte $byte + * @return bool + */ + public function equals(Byte $byte): bool + { + return $this->value === $byte->getValue(); + } + + /** + * Determine if this byte is greater than another byte. + * + * @param Byte $byte + * @return bool + */ + public function isGreaterThan(Byte $byte): bool + { + return $this->value > $byte->getValue(); + } + + /** + * Determine if this byte is less than another byte. + * + * @param Byte $byte + * @return bool + */ + public function isLessThan(Byte $byte): bool + { + return $this->value < $byte->getValue(); + } + + /** + * Get the binary string representation of the byte. + * + * @return string + */ + public function toBinary(): string + { + return sprintf('%08b', $this->value); + } + + /** + * Get the hexadecimal string representation of the byte. + * + * @return string + */ + public function toHex(): string + { + return sprintf('%02X', $this->value); + } + + /** + * Convert the byte value to a string. + * + * @return string + */ + public function __toString(): string + { + return (string) $this->value; + } + + /** + * Create a byte instance from a binary string. + * + * @param string $binary + * @return Byte + */ + public static function fromBinary(string $binary): Byte + { + return new self(bindec($binary)); + } + + /** + * Create a byte instance from a hexadecimal string. + * + * @param string $hex + * @return Byte + */ + public static function fromHex(string $hex): Byte + { + return new self(hexdec($hex)); + } + + /** + * Add an integer to the byte value, wrapping around at 255. + * + * @param int $number + * @return Byte + */ + public function add(int $number): Byte + { + return new self(($this->value + $number) & 0xFF); // Wrap around at 255 + } + + /** + * Subtract an integer from the byte value, wrapping around at 0. + * + * @param int $number + * @return Byte + */ + public function subtract(int $number): Byte + { + return new self(($this->value - $number) & 0xFF); // Wrap around at 0 + } } diff --git a/src/Scalar/Char.php b/src/Scalar/Char.php index 4f3b75f..a54cdd9 100644 --- a/src/Scalar/Char.php +++ b/src/Scalar/Char.php @@ -1,18 +1,147 @@ value = $value; } - public function getValue(): string { + /** + * Get the character value. + * + * @return string + */ + public function getValue(): string + { + return $this->value; + } + + /** + * Convert the character to its uppercase representation. + * + * @return Char + */ + public function toUpperCase(): Char + { + return new self(strtoupper($this->value)); + } + + /** + * Convert the character to its lowercase representation. + * + * @return Char + */ + public function toLowerCase(): Char + { + return new self(strtolower($this->value)); + } + + /** + * Determine if the character is a letter. + * + * @return bool + */ + public function isLetter(): bool + { + return ctype_alpha($this->value); + } + + /** + * Determine if the character is a digit. + * + * @return bool + */ + public function isDigit(): bool + { + return ctype_digit($this->value); + } + + /** + * Determine if the character is an uppercase letter. + * + * @return bool + */ + public function isUpperCase(): bool + { + return ctype_upper($this->value); + } + + /** + * Determine if the character is a lowercase letter. + * + * @return bool + */ + public function isLowerCase(): bool + { + return ctype_lower($this->value); + } + + /** + * Compare the current character with another Char instance. + * + * @param Char $char + * @return bool + */ + public function equals(Char $char): bool + { + return $this->value === $char->getValue(); + } + + /** + * Convert the character to its ASCII code. + * + * @return int + */ + public function toAscii(): int + { + return ord($this->value); + } + + /** + * Convert the ASCII code to a Char. + * + * @param int $ascii + * @return Char + */ + public static function fromAscii(int $ascii): Char + { + if ($ascii < 0 || $ascii > 255) { + throw new \InvalidArgumentException('ASCII value must be between 0 and 255.'); + } + + return new self(chr($ascii)); + } + + /** + * Convert the character to a string. + * + * @return string + */ + public function __toString(): string + { return $this->value; } }