Skip to content

Commit

Permalink
Merge pull request #23 from m3m0r7/add-object-and-regexp
Browse files Browse the repository at this point in the history
Implementation of Hash and Regexp
  • Loading branch information
m3m0r7 authored Sep 29, 2023
2 parents 1521cbc + 32dfeca commit 9a1728f
Show file tree
Hide file tree
Showing 24 changed files with 468 additions and 65 deletions.
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ parameters:
- '#[^:]+::__debugInfo\(\) return type has no value type specified in iterable type array#'
- '#Class [^\s]+ has an uninitialized readonly property \$.+?\. Assign it in the constructor#'
- '#Readonly property [^\s]+ is assigned outside of the constructor#'
- '#Attribute class [^\s]+ does not have a constructor and must be instantiated without any parameters#'
8 changes: 8 additions & 0 deletions src/VM/Core/Runtime/Attribute/BindAliasAs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

declare(strict_types=1);

namespace RubyVM\VM\Core\Runtime\Attribute;

#[\Attribute]
class BindAliasAs {}
30 changes: 30 additions & 0 deletions src/VM/Core/Runtime/BasicObject/BasicObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace RubyVM\VM\Core\Runtime\BasicObject;

use RubyVM\VM\Core\Runtime\Entity\Class_;
use RubyVM\VM\Core\Runtime\Entity\EntityInterface;
use RubyVM\VM\Core\Runtime\Essential\RubyClassInterface;
use RubyVM\VM\Core\YARV\Criterion\ShouldBeRubyClass;
use RubyVM\VM\Core\YARV\Essential\Symbol\StringSymbol;

abstract class BasicObject implements RubyClassInterface
{
use ShouldBeRubyClass;

protected ?EntityInterface $entity = null;

public function entity(): EntityInterface
{
return $this->entity ??= Class_::createBy(new StringSymbol((string) $this));
}

public function __toString(): string
{
$classNames = explode('\\', static::class);

return array_pop($classNames);
}
}
9 changes: 9 additions & 0 deletions src/VM/Core/Runtime/BasicObject/Kernel/Kernel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace RubyVM\VM\Core\Runtime\BasicObject\Kernel;

use RubyVM\VM\Core\Runtime\BasicObject\BasicObject;

abstract class Kernel extends BasicObject {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace RubyVM\VM\Core\Runtime\BasicObject\Kernel\Object_\Enumerable;

use RubyVM\VM\Core\Runtime\BasicObject\Kernel\Object_\Object_;

abstract class Enumerable extends Object_ {}
36 changes: 36 additions & 0 deletions src/VM/Core/Runtime/BasicObject/Kernel/Object_/Enumerable/Hash.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace RubyVM\VM\Core\Runtime\BasicObject\Kernel\Object_\Enumerable;

use RubyVM\VM\Core\Runtime\Essential\RubyClassInterface;
use RubyVM\VM\Core\YARV\Essential\Symbol\SymbolInterface;

class Hash extends Enumerable implements RubyClassInterface, \ArrayAccess
{
/**
* @param array<SymbolInterface> $hash
*/
public function __construct(protected array $hash = []) {}

public function offsetExists(mixed $offset): bool
{
return array_key_exists($offset, $this->hash);
}

public function offsetGet(mixed $offset): mixed
{
return $this->hash[$offset] ?? null;
}

public function offsetSet(mixed $offset, mixed $value): void
{
$this->hash[$offset] = $value;
}

public function offsetUnset(mixed $offset): void
{
unset($this->hash[$offset]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,18 @@

declare(strict_types=1);

namespace RubyVM\VM\Core\Runtime;
namespace RubyVM\VM\Core\Runtime\BasicObject\Kernel\Object_;

use RubyVM\VM\Core\Runtime\Entity\Class_;
use RubyVM\VM\Core\Runtime\Entity\EntityInterface;
use RubyVM\VM\Core\Runtime\Essential\MainInterface;
use RubyVM\VM\Core\Runtime\Essential\RubyClassInterface;
use RubyVM\VM\Core\Runtime\Executor\Executor;
use RubyVM\VM\Core\Runtime\Option;
use RubyVM\VM\Core\YARV\Criterion\InstructionSequence\CallInfoInterface;
use RubyVM\VM\Core\YARV\Criterion\InstructionSequence\InstructionSequenceInterface;
use RubyVM\VM\Core\YARV\Criterion\ShouldBeRubyClass;
use RubyVM\VM\Core\YARV\Essential\Symbol\StringSymbol;

class Lambda implements MainInterface
class Lambda extends Object_ implements RubyClassInterface
{
use ShouldBeRubyClass;

protected ?EntityInterface $entity = null;

public function __construct(private readonly InstructionSequenceInterface $instructionSequence) {}

public function entity(): EntityInterface
{
return $this->entity ??= Class_::createBy(new StringSymbol('lambda'));
}

public function __toString(): string
{
return 'lambda';
}

public function call(CallInfoInterface $callInfo, RubyClassInterface ...$arguments): RubyClassInterface|null
{
$executor = new Executor(
Expand Down
9 changes: 9 additions & 0 deletions src/VM/Core/Runtime/BasicObject/Kernel/Object_/Object_.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace RubyVM\VM\Core\Runtime\BasicObject\Kernel\Object_;

use RubyVM\VM\Core\Runtime\BasicObject\Kernel\Kernel;

abstract class Object_ extends Kernel {}
2 changes: 2 additions & 0 deletions src/VM/Core/Runtime/Entity/Boolean_.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace RubyVM\VM\Core\Runtime\Entity;

use RubyVM\VM\Core\Runtime\Attribute\BindAliasAs;
use RubyVM\VM\Core\YARV\Essential\Symbol\BooleanSymbol;

class Boolean_ extends Entity implements EntityInterface
Expand All @@ -13,6 +14,7 @@ public function __construct(BooleanSymbol $symbol)
$this->symbol = $symbol;
}

#[BindAliasAs('to_s')]
public function toString(): String_
{
return String_::createBy(
Expand Down
2 changes: 2 additions & 0 deletions src/VM/Core/Runtime/Entity/EntityHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use RubyVM\VM\Core\YARV\Essential\Symbol\NumberSymbol;
use RubyVM\VM\Core\YARV\Essential\Symbol\OffsetSymbol;
use RubyVM\VM\Core\YARV\Essential\Symbol\RangeSymbol;
use RubyVM\VM\Core\YARV\Essential\Symbol\RegExpSymbol;
use RubyVM\VM\Core\YARV\Essential\Symbol\StringSymbol;
use RubyVM\VM\Core\YARV\Essential\Symbol\SymbolInterface;
use RubyVM\VM\Core\YARV\Essential\Symbol\SymbolSymbol;
Expand All @@ -37,6 +38,7 @@ public static function createEntityBySymbol(SymbolInterface $symbol): EntityInte
UndefinedSymbol::class => new Undefined($symbol),
VoidSymbol::class => new Void_($symbol),
SymbolSymbol::class => new Symbol($symbol),
RegExpSymbol::class => new RegExp($symbol),
default => throw new EntityException(
sprintf(
'The specified entity was not implemented yet: %s',
Expand Down
8 changes: 7 additions & 1 deletion src/VM/Core/Runtime/Entity/Nil.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ public function __construct(NilSymbol $symbol)
$this->symbol = $symbol;
}

public static function createBy(mixed $value = null): EntityInterface
public function testValue(): bool
{
// Always return false
return false;
}

public static function createBy(mixed $value = null): self
{
static $symbol = new NilSymbol();

Expand Down
8 changes: 8 additions & 0 deletions src/VM/Core/Runtime/Entity/Number.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace RubyVM\VM\Core\Runtime\Entity;

use RubyVM\VM\Core\Runtime\Attribute\BindAliasAs;
use RubyVM\VM\Core\Runtime\Essential\RubyClassInterface;
use RubyVM\VM\Core\YARV\Criterion\InstructionSequence\CallInfoInterface;
use RubyVM\VM\Core\YARV\Essential\Symbol\NumberSymbol;
Expand All @@ -20,46 +21,53 @@ public function testValue(): bool
return (bool) $this->symbol->valueOf();
}

#[BindAliasAs('^')]
public function xor(CallInfoInterface $callInfo, RubyClassInterface $object): Number
{
return Number::createBy(
$this->symbol->valueOf() ^ $object->entity()->symbol()->valueOf(),
);
}

#[BindAliasAs('**')]
public function power(CallInfoInterface $callInfo, RubyClassInterface $object): Number
{
return Number::createBy(
$this->symbol->valueOf() ** $object->entity()->symbol()->valueOf(),
);
}

#[BindAliasAs('>>')]
public function rightShift(CallInfoInterface $callInfo, RubyClassInterface $object): Number
{
return Number::createBy(
$this->symbol->valueOf() >> $object->entity()->symbol()->valueOf(),
);
}

#[BindAliasAs('===')]
public function compareStrictEquals(CallInfoInterface $callInfo, RubyClassInterface $object): Boolean_
{
return Boolean_::createBy(
$this->symbol->valueOf() === $object->entity()->symbol()->valueOf(),
);
}

#[BindAliasAs('to_i')]
public function toInt(): self
{
return clone $this;
}

#[BindAliasAs('to_f')]
public function toFloat(): Float_
{
return Float_::createBy(
(float) $this->symbol->valueOf(),
);
}

#[BindAliasAs('to_s')]
public function toString(): String_
{
return String_::createBy(
Expand Down
54 changes: 54 additions & 0 deletions src/VM/Core/Runtime/Entity/RegExp.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace RubyVM\VM\Core\Runtime\Entity;

use RubyVM\VM\Core\Runtime\Attribute\BindAliasAs;
use RubyVM\VM\Core\YARV\Criterion\InstructionSequence\CallInfoInterface;
use RubyVM\VM\Core\YARV\Essential\Symbol\RegExpSymbol;

class RegExp extends Entity implements EntityInterface
{
public function __construct(RegExpSymbol $symbol)
{
$this->symbol = $symbol;
}

public static function createBy(mixed $value = null, int $option = null): EntityInterface
{
return new self(new RegExpSymbol($value, $option));
}

public function __toString(): string
{
// TODO: Convert Ruby regexp to PCRE
return '/' . $this->symbol->valueOf()->valueOf() . '/';
}

/**
* @see https://docs.ruby-lang.org/ja/latest/class/Regexp.html#I_--3D--7E
*/
#[BindAliasAs('=~')]
public function equalsTilde(CallInfoInterface $callInfo, String_|Nil $source): Number|Nil
{
if ($source instanceof Nil) {
return Nil::createBy();
}

preg_match(
(string) $this,
(string) $source,
$match,
PREG_OFFSET_CAPTURE,
);

if ($match === []) {
return Nil::createBy();
}

[, $offset] = $match[0];

return Number::createBy($offset);
}
}
21 changes: 19 additions & 2 deletions src/VM/Core/Runtime/Executor/Insn/Processor/BuiltinNewhash.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@

namespace RubyVM\VM\Core\Runtime\Executor\Insn\Processor;

use RubyVM\VM\Core\Runtime\BasicObject\Kernel\Object_\Enumerable\Hash;
use RubyVM\VM\Core\Runtime\Essential\RubyClassInterface;
use RubyVM\VM\Core\Runtime\Executor\Context\ContextInterface;
use RubyVM\VM\Core\Runtime\Executor\Insn\Insn;
use RubyVM\VM\Core\Runtime\Executor\Operation\Operand;
use RubyVM\VM\Core\Runtime\Executor\Operation\OperandHelper;
use RubyVM\VM\Core\Runtime\Executor\Operation\Processor\OperationProcessorInterface;
use RubyVM\VM\Core\Runtime\Executor\ProcessedStatus;
use RubyVM\VM\Exception\OperationProcessorException;

class BuiltinNewhash implements OperationProcessorInterface
{
Expand All @@ -31,6 +32,22 @@ public function after(): void {}

public function process(ContextInterface|RubyClassInterface ...$arguments): ProcessedStatus
{
throw new OperationProcessorException(sprintf('The `%s` (opcode: 0x%02x) processor is not implemented yet', strtolower($this->insn->name), $this->insn->value));
$number = $this->getOperandAsNumber();
$newHash = new Hash();
for ($i = 0; $i < ($number->valueOf() / 2); ++$i) {
$value = $this->getStackAsEntity();
$name = $this->getStackAsSymbol();

$newHash[(string) $name] = $value
->symbol();
}

$this->context->vmStack()->push(
new Operand(
$newHash,
)
);

return ProcessedStatus::SUCCESS;
}
}
Loading

0 comments on commit 9a1728f

Please sign in to comment.