m3m0r7/rubyvm-on-php

0.3.3.0 2023-12-28 04:25 UTC

This package is auto-updated.

Last update: 2024-10-01 00:20:41 UTC


README

PHP上的RubyVM是完全用PHP编写的RubyVM实现,100%。没有完全的文档说明如何实现RubyVM,我在贡献这个项目时参考了Ruby源代码

注意:这个项目是一个非常超超超超实验性的实现

注意:我只测试了Ruby版本3.2和3.3

另请参阅

DEMO

需求

  • PHP 8.2+

当前状态

  • 实现了通用语法(定义局部变量、全局变量、类、方法、布尔值、哈希表、数组等)
  • 实现了算术运算(+、-、*、/),位运算(|、&、<<、>>),一些运算符(**、%)并可重载
  • 实现了块语法([].each do | var | ... end)和非块语法([].push
  • 实现了方法调用时的关键字参数(keyword_argument(a: "Hello", c: "!", b: "World")
  • 实现了使用数组和调用方法时的可变参数([*var1, *var2]keyword_argument(a, b, *c)
  • 实现了部分Ruby方法(to_sto_i[].pushfoobar.nil?
  • 实现了case-when语法
  • 实现了正则表达式语法(p /Hello/ =~ "Hello World"
  • 实现了raise/rescue
  • 等等(见测试目录)

快速开始

  1. 按以下方式使用composer安装
$ composer require m3m0r7/rubyvm-on-php
  1. 将以下代码保存为 HelloWorld.rb
puts RubyVM::InstructionSequence.compile("puts 'HelloWorld!\n'", "HelloWorld.rb").to_binary
  1. 按以下方式输出 .yarv 文件
$ ruby HelloWorld.rb > HelloWorld.yarv
  1. 创建以下代码的PHP文件并保存为 HelloWorld.php
<?php
require __DIR__ . '/vendor/autoload.php';

// Instantiate RubyVM class
$rubyVM = new \RubyVM\VM\Core\Runtime\RubyVM(
    new \RubyVM\VM\Core\Runtime\Option(
        reader: new \RubyVM\Stream\BinaryStreamReader(
            streamHandler: new \RubyVM\Stream\FileStreamHandler(
                // Specify to want you to load YARV file
                __DIR__ . '/HelloWorld.yarv',
            ),
        ),

        // Choose Logger
        logger: new \Psr\Log\NullLogger(),
    ),
);

// Disassemble instruction sequence binary formatted and get executor
$executor = $rubyVM->disassemble();

// You can choose to run ruby version if you needed
// $executor = $rubyVM->disassemble(
//    useVersion: \RubyVM\RubyVersion::VERSION_3_2,
// );

// Execute disassembled instruction sequence
$executor->execute();
  1. 运行 php HelloWorld.php,你将得到RubyVM输出的 HelloWorld!

在PHP上调用定义的ruby方法

  1. 创建以下ruby代码
def callFromPHP
  puts "Hello World from Ruby!"
end

然后,将文件保存为 test.rb

  1. 按以下方式编译到YARV
$ ruby -e "puts RubyVM::InstructionSequence.compile_file('test.rb').to_binary" > test.yarv
  1. 按以下方式在PHP上调用ruby方法
<?php
require __DIR__ . '/vendor/autoload.php';

// Instantiate RubyVM class
$rubyVM = new \RubyVM\VM\Core\Runtime\RubyVM(
    new \RubyVM\VM\Core\Runtime\Option(
        reader: new \RubyVM\Stream\BinaryStreamReader(
            streamHandler: new \RubyVM\Stream\FileStreamHandler(
                // Specify to want you to load YARV file
                __DIR__ . '/test.yarv',
            ),
        ),

        // Choose Logger
        logger: new \Psr\Log\NullLogger(),
    ),
);

// Disassemble instruction sequence binary formatted and get executor
$executor = $rubyVM->disassemble();

// Execute disassembled instruction sequence
$executed = $executor->execute();

// Call Ruby method as below code.
// In this case, you did define method name is `callFromPHP`.
$executed->context()->callFromPHP();

你将得到输出 Hello World from Ruby!。此外,在这种情况下,你可能想传递参数。当然,这是可用的。第一次,修改前面的代码如下。

def callFromPHP(text)
  puts text
end

第二次,修改PHP代码 $executed->context()->callFromPHP() 如下

$executed->context()->callFromPHP('Hello World! Here is passed an argument from PHP!')

你将得到输出 Hello World! Here is passed an argument from PHP

使用执行器调试器

RubyVM on PHP提供了一个执行器调试器,可以将处理过的INSN和其他信息以表格形式显示如下

+-----+--------------------------------+-------------------------------+--------------------------------+--------------+
| PC  | CALLEE                         | INSN                          | CURRENT STACKS                 | LOCAL TABLES |
+-----+--------------------------------+-------------------------------+--------------------------------+--------------+
| 0   | <main>                         | [0x12] putself                |                                |              |
+-----+--------------------------------+-------------------------------+--------------------------------+--------------+
| 1   | <main>                         | [0x15] putstring              | Main#0                         |              |
+-----+--------------------------------+-------------------------------+--------------------------------+--------------+
| 3   | <main>                         | [0x33] opt_send_without_block | Main#0, String(Hello World!)#1 |              |
|     |                                | (Main#puts(Hello World!))     |                                |              |
+-----+--------------------------------+-------------------------------+--------------------------------+--------------+
| 5   | <main>                         | [0x3c] leave                  | Nil(nil)#0                     |              |
+-----+--------------------------------+-------------------------------+--------------------------------+--------------+

如果你想要显示上述表格,请从快速开始中添加以下代码。

注意:执行器调试器会消耗大量内存。我们建议通常禁用。根据具体情况,可能需要使用-d memory_limit=NEEDING_MEMORY_BYTES参数在调用php命令时使其正常工作

// You can display processed an INSN table when adding below code
$executor->context()->option()->debugger()->showExecutedOperations();

逐步调试

RubyVM on PHP提供了一个逐步调试器。它可以逐步确认处理过程。它会收集之前的堆栈、注册的本地表等信息。这对于调试这个项目是必要的。

// Use breakpoint debugger with option

$rubyVM = new \RubyVM\VM\Core\Runtime\RubyVM(
    new \RubyVM\VM\Core\Runtime\Option(
        // excluded...

        debugger: new \RubyVM\VM\Core\Runtime\Executor\Debugger\StepByStepDebugger(),
    ),
);

当你启用它时,将显示如下

+-----+--------------------------------+-------------------------------+--------------------------------+--------------+
| PC  | CALLEE                         | INSN                          | CURRENT STACKS                 | LOCAL TABLES |
+-----+--------------------------------+-------------------------------+--------------------------------+--------------+
| 0   | <main>                         | [0x12] putself                |                                |              |
+-----+--------------------------------+-------------------------------+--------------------------------+--------------+
| 1   | <main>                         | [0x15] putstring              | Main#0                         |              |
+-----+--------------------------------+-------------------------------+--------------------------------+--------------+
Current INSN: putstring(0x15)
Previous Stacks: [total: 1, OperandEntry<Main>]#966
Previous Local Tables: []
Current Stacks: [total: 2, OperandEntry<Main>, OperandEntry<StringSymbol@HelloWorld!>]#561
Current Local Tables: []

Enter to next step (y/n/q): <INPUT_YOU_EXPECTING_NEXT_STEP>

自定义方法

RubyVM on PHP在主上下文中具有自定义方法。尝试在RubyVM on PHP上使用以下Ruby代码调用phpinfo

phpinfo

然后你将看到显示PHP Version: 8.2.7

测试

$ ./vendor/bin/phpunit tests/

代码检查器

./vendor/bin/php-cs-fixer fix --allow-risky=yes

如何贡献

  1. 使用-DIBF_ISEQ_DEBUG标志从源代码构建你的ruby环境

参见: https://docs.ruby-lang.cn/en/master/contributing/building_ruby_md.html

$ git clone git@github.com:ruby/ruby.git
$ mkdir build && cd build
$ ../configure cppflags="-DIBF_ISEQ_DEBUG=1"
$ make -j$(nproc)
  1. 当您构建 Ruby 环境时,将会得到一个 vm.inc 文件,该文件记录了如何执行每个 INSN 命令

  2. 当按照以下方式运行 Ruby 代码时,您可以在 ibf_load_** 获取日志

...omitted

ibf_load_object: type=0x15 special=1 frozen=1 internal=1      // The type is a FIX_NUMBER (2)
ibf_load_object: index=0x3 obj=0x5
ibf_load_object: list=0xf0 offsets=0x12b80fcf0 offset=0xe1
ibf_load_object: type=0x15 special=1 frozen=1 internal=1      // The type is a FIX_NUMBER (3)
ibf_load_object: index=0x4 obj=0x7
ibf_load_object: list=0xf0 offsets=0x12b80fcf0 offset=0xcd
ibf_load_object: type=0x5 special=0 frozen=1 internal=0       // The type is a STRING SYMBOL (puts)

...omitted

上述日志是由以下示例代码创建的

puts 1 + 2 + 3
  1. 参考它,现在您可以贡献于在 PHP 上的 RubyVM 实现INSN 命令

我的其他玩具

  • PHPJava - 使用 PHP 实现的 JVM
  • nfc-for-php - 使用 PHP 编写的 NFC 读取器(控制 NFC 硬件)
  • PHPPython - 使用 PHP 实现的 PYC 执行器