M src/Bare.php => src/Bare.php +3 -2
@@ 16,6 16,8 @@ class Bare
$val = substr($mess, 0, $length);
$mess = substr($mess, $length);
return $val;
+ case 'integer': // int
+ return (new Integer($mess))->get();
};
}
@@ 32,8 34,7 @@ class Bare
// TODO: Use uint for string's length.
return UInt::fromValue(strlen($value))->mess() . $value;
case 'integer': // int
- // TODO: encode as variable length integer.
- return chr($value);
+ return Integer::fromValue($value)->mess();
case 'double': // f64
// TODO: encode this.
return chr(0) . chr(0) . chr(0) . chr(0);
A src/Integer.php => src/Integer.php +56 -0
@@ 0,0 1,56 @@
+<?php
+
+namespace BareMess;
+
+// We can't use "Int" as the name of this class as it's reserved by PHP.
+// Note: Bare::mess and Bare::construct will encode a PHP int as a \BareMess\Integer.
+class Integer
+{
+ use Mutatable;
+ private int $value = 0;
+
+ public function __construct(& $mess = null)
+ {
+ if ($mess === null){
+ return;
+ }
+ $this->value = 0;
+ // The LSB is the sign.
+ $sign = (ord($mess[0]) % 2) ? -1 : 1;
+ // We can't do this inside the loop because bitshifting by negative numbers is not allowed.
+ $this->value += (ord($mess[0]) & 0b01111110) >> 1;
+ for($i = 1; $i < strlen($mess) + 1; $i++) {
+ if (! (ord($mess[$i - 1]) & 0b10000000) ) {
+ $mess = substr($mess, $i);
+ $this->value *= $sign;
+ return;
+ }
+ // TODO: Throw an error if $mess is empty here.
+ // Least significant bits first
+ $this->value += (ord($mess[$i]) & 0b01111111) << (($i * 7) - 1);
+ }
+ }
+
+ public function mess()
+ {
+ $mess = '';
+ // The number of bytes should be greater than or equal to the number required,
+ // abs(value * 2) < pow(2, 7 * bytes)
+ $bytes = floor(log(abs($this->value) + 1, 2) / 7) + 1;
+ $bits = (abs($this->value) << 1 & 0b01111110) + ($this->value < 0);
+ if ($bytes != 1) {
+ $bits = $bits | 0b10000000;
+ }
+ $mess .= chr($bits);
+ for ($i = 1; $i < $bytes; $i++) {
+ // BARE encodes integers as little-endian (least significant first),
+ $bits = (abs($this->value) >> (($i * 7)-1)) & 0b01111111;
+ // with the most significant bit being set for all except the last byte.
+ if ($i != $bytes - 1) {
+ $bits = $bits | 0b10000000;
+ }
+ $mess .= chr($bits);
+ }
+ return $mess;
+ }
+}
M tests/PrimitivesTest.php => tests/PrimitivesTest.php +34 -0
@@ 6,6 6,7 @@ use BareMess\Bare;
use BareMess\Example\Department;
use BareMess\Example\PublicKey;
use BareMess\{I8,I16,I32,I64};
+use BareMess\Integer;
use BareMess\{U8,U16,U32,U64};
use BareMess\UInt;
use PHPUnit\Framework\TestCase;
@@ 59,6 60,39 @@ class PrimitivesTest extends TestCase
$this->assertEquals("\x03", $dept->mess());
}
+ public function testInteger()
+ {
+ // Various equivalent ints.
+ $mess = "\xC1\x80\x00\xC1\x00\x41";
+ $int = Bare::construct($mess, 0);
+ $this->assertEquals(-32, $int);
+ $int2 = Bare::construct($mess, 0);
+ $this->assertEquals(-32, $int2);
+ $int3 = Bare::construct($mess, 0);
+ $this->assertEquals(-32, $int3);
+ $this->assertEquals('', $mess);
+
+ $int = 300;
+ $this->assertEquals(UInt::fromValue(600)->mess(), Bare::mess($int));
+
+ // This is usually PHP_INT_MIN - 1 (BARE's ints are zig-zag encoded).
+ $int = Integer::fromValue(-9223372036854775807);
+ // Test round-tripping
+ $mess = $int->mess();
+ $int2 = new Integer($mess);
+ $this->assertEquals('', $mess);
+ $this->assertEquals(-9223372036854775807, $int->get());
+ $this->assertEquals(-9223372036854775807, $int2->get());
+
+ // Test largest value
+ $int = Integer::fromValue(9223372036854775807);
+ $mess = $int->mess();
+ $int2 = new Integer($mess);
+ $this->assertEquals('', $mess);
+ $this->assertEquals(9223372036854775807, $int->get());
+ $this->assertEquals(9223372036854775807, $int2->get());
+ }
+
public function testIs()
{
$i8 = new I8();