Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 26 additions & 21 deletions src/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,41 @@ public function __construct(protected Registry $registry)

public function parse(string $input): Unit
{
if ($this->registry->has($input)) {
return $this->registry->get($input);;
}

$tokens = $this->tokenize($input);
$parts = [];

foreach ($tokens as $i => $token) {
$prevToken = $tokens[$i - 1] ?? null;

if ($token['type'] === 'unit') {
$unit = $this->registry->get($token['value']);
if ($token['type'] !== 'unit') {
continue;
}

if ($unit === null) {
throw new InvalidUnitException("Unknown unit: {$token['value']}");
}
$unit = $this->registry->get($token['value']);

$powerSign = $prevToken && $prevToken['type'] === 'operator' && $prevToken['value'] === '/'
? -1
: 1;

$parts[] = array_map(function (UnitPart|FactorUnitPart $part) use ($token, $powerSign) {
if ($part instanceof FactorUnitPart) {
return new FactorUnitPart($part->getRatio());
}

return new UnitPart(
$part->getRatio(),
$part->getDimension(),
$part->getPower() * $token['power'] * $powerSign,
);
}, $unit->getParts());
if ($unit === null) {
throw new InvalidUnitException("Unknown unit: {$token['value']}");
}
$powerSign = $prevToken && $prevToken['type'] === 'operator' && $prevToken['value'] === '/'
? -1
: 1;
$power = $token['power'] * $powerSign;

$parts[] = array_map(function (UnitPart|FactorUnitPart $part) use ($power) {
if ($part instanceof FactorUnitPart) {
return new FactorUnitPart($part->getRatio());
}

return new UnitPart(
$part->getRatio(),
$part->getDimension(),
$part->getPower() * $power,
);
}, $unit->getParts());
}

return new Unit(...array_merge([], ...$parts));
Expand All @@ -59,7 +65,6 @@ protected function tokenize(string $input): array
PREG_SET_ORDER
);


foreach ($matches as $match) {
if ($match['unit']) {
$tokens[] = [
Expand Down
75 changes: 75 additions & 0 deletions tests/ParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
use Vesper\UnitConversion\Registry;
use Vesper\UnitConversion\RegistryBuilder;
use Vesper\UnitConversion\Exceptions\InvalidUnitException;
use Vesper\UnitConversion\Unit;
use Vesper\UnitConversion\UnitPart;

final class ParserTest extends TestCase
{
Expand Down Expand Up @@ -175,4 +177,77 @@ public function test_throws_on_unknown_token()

$this->parser->parse('kilometer / yeet');
}

public function test_parses_dash_separated_units()
{
$result = $this->parser->parse('gram-liter');
[$gram, $liter] = $result->getParts();

$this->assertCount(2, $result->getParts());

$this->assertEquals(Dimension::MASS, $gram->getDimension());
$this->assertEquals(0.001, $gram->getRatio());
$this->assertEquals(1, $gram->getPower());

$this->assertEquals(Dimension::LENGTH, $liter->getDimension());
$this->assertEquals(0.1, $liter->getRatio());
$this->assertEquals(3, $liter->getPower());
}

public function test_parses_dash_separated_unit_with_operator()
{
$result = $this->parser->parse('gram-liter/hour');
[$gram, $liter, $hour] = $result->getParts();

$this->assertCount(3, $result->getParts());

$this->assertEquals(Dimension::MASS, $gram->getDimension());
$this->assertEquals(0.001, $gram->getRatio());
$this->assertEquals(1, $gram->getPower());

$this->assertEquals(Dimension::LENGTH, $liter->getDimension());
$this->assertEquals(0.1, $liter->getRatio());
$this->assertEquals(3, $liter->getPower());

$this->assertEquals(Dimension::TIME, $hour->getDimension());
$this->assertEquals(3600, $hour->getRatio());
$this->assertEquals(-1, $hour->getPower());
}

public function test_parses_dash_joined_single_unit_directly_if_registered()
{
$wattHour = $this->registry->get('watt-hour');
$result = $this->parser->parse('watt-hour');
[$wattMass, $wattLength, $wattTime, $hour] = $result->getParts();

$this->assertCount(4, $result->getParts());
$this->assertEquals($wattMass, $wattHour->getPart(0));
$this->assertEquals($wattLength, $wattHour->getPart(1));
$this->assertEquals($wattTime, $wattHour->getPart(2));
$this->assertEquals($hour, $wattHour->getPart(3));
}

public function test_parses_dash_joined_single_unit_with_operator()
{
$wattHour = $this->registry->get('watt-hour');
$result = $this->parser->parse('watt-hour/kg');
[$wattMass, $wattLength, $wattTime, $hour, $kilo, $gram] = $result->getParts();

$this->assertCount(6, $result->getParts());

// Watt-hour
$this->assertEquals($wattMass, $wattHour->getPart(0));
$this->assertEquals($wattLength, $wattHour->getPart(1));
$this->assertEquals($wattTime, $wattHour->getPart(2));
$this->assertEquals($hour, $wattHour->getPart(3));

// Kilogram
$this->assertNull($kilo->getDimension());
$this->assertEquals(1000, $kilo->getRatio());
$this->assertEquals(1, $kilo->getPower());

$this->assertEquals(Dimension::MASS, $gram->getDimension());
$this->assertEquals(0.001, $gram->getRatio());
$this->assertEquals(-1, $gram->getPower());
}
}