MRubyD:一个用纯C#实现的新型mruby虚拟机
MRubyD: A new mruby virtual machine implemented in pure C#

原始链接: https://github.com/hadashiA/MRubyD

MRubyD是一个用纯C#实现的全新mruby虚拟机,旨在与基于C#的游戏引擎无缝集成。它优先考虑Ruby级API兼容性,同时利用现代C#特性(如托管指针和Span)来提高性能和可扩展性。可以轻松地从Ruby访问C#库。 目前处于预览阶段,MRubyD支持所有mruby操作码并通过了语法测试,但内置类型、方法和可见性仍在开发中。该项目仅提供虚拟机;编译需要原生的mruby编译器。 `MRubyD.Compiler`包通过提供原生编译器的包装器来简化C#编译,支持Linux、macOS和Windows。该库能够在C#中执行预编译的mruby字节码(`.mrb`),提供与C#工作流程的无缝集成。它提供创建Ruby值、定义类/模块/方法以及处理UTF-8字符串和符号的实用程序。

Hacker News 最新 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 MRubyD:一个用纯C#实现的新的mruby虚拟机 (github.com/hadashia) 9 分,来自 ksec,2 小时前 | 隐藏 | 过去 | 收藏 | 2 条评论 evolve2k 35 分钟前 [–] > “……为了确保与基于C#的游戏引擎无缝集成。”他们可能想到了哪些游戏引擎? 回复 mastax 32 分钟前 | 父评论 [–] 肯定有Unity。 回复 加入我们,参加6月16日至17日在旧金山举办的AI创业学校! 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请YC | 联系方式 搜索:

原文

MRubyD is a new mruby virtual machine implemented in pure C#. The name "MRubyD" stands for mruby for dotnet and pays homage to the well-known alternative implementation mruby/c. Designed with seamless integration in mind for C#-based game engines, and emphasize ruby level compatibility. MRubyD leverages the latest C# features for high performance and high extensibility.

Note

This library is currently in preview

  • Implemented in C#
    Utilizes the robust capabilities of C# to ensure seamless integration with C#-based game engines.

  • High Performance
    Takes advantage of modern C# language features—such as managed pointers, Span, and the performance benefits of the .NET runtime’s GC and JIT compiler—to deliver superior speed and efficiency.

  • High compatibility with Ruby-level APIs (Work in progress) It is intended for use in software with a certain amount of resources, such as games. For this reason, we are focusing on Ruby API compatibility. At release, all opcodes are implemented and pass the syntax.rb tests from the mruby repository.

  • Rich Library Integration & Extensibility
    Compared to the original C implementation, calling C#’s extensive libraries from Ruby is straightforward, making the VM highly extensible.

Limitations (Preview Release)

This release is a preview version and comes with the following constraints:

  • Built-in types and methods are still being implemented.
    • Please refer to ruby test, etc., for currently supported methods.
    • We are working on supporting all methods that are built into mruby by default.
  • private and protected visibitily is not yet implemented. (mruby got support for this in 3.4)
  • This project provides only the VM implementation; it does not include a compiler. To compile mruby scripts, you need the native mruby-compiler.
dotnet add package MRubyD
def fibonacci(n)
  return n if n <= 1 
  fibonacci(n - 1) + fibonacci(n - 2)
end

fibonacci 10
$ mrbc -o fibonaci.mrbc fibonacci.rb
using MRubyD;

// Read the .mrb byte-code.
var bytes = File.ReadAllBytes("fibonacci.mrb");

// initialize state
var state = MRubyState.Create();

// execute bytecoe
var result = state.Exec(bytes);

result.IsInteger    //=> true
result.IntegerValue //=> 55

This is a sample of executing bytecode. See the How to compile .mrb section for information on how to convert Ruby source code to mruby bytecode.

Above result is MRubyValue. This represents a Ruby value.

value.IsNil //=> true if nol
value.IsInteger //=> true if integrr
value.IsFloat //=> true if float
value.IsSymbol //=> true if Symbol
value.IsObject //=> true if any allocated object type

value.VType //=> get known ruby-type as C# enum.

value.IntegerValue //=> get as C# Int64
value.FloatValue //=> get as C# float
value.SymbolValue //=> get as `Symbol`

value.As<RString>() //=> get as object value

// pattern matching
if (vlaue.Object is RString str)
{
    // ...
}

swtich (value)
{
    case { IsInteger: true }:
        // ...
        break;
    case { Object: RString str }:
        // ...
        break;
}

var intValue = MRubyValue.From(100); // create int value
var floatValue = MRubyValue.From(1.234f); // create float value
var objValue = MRubyValue.From(str); // create allocated ruby object value

Define ruby class/module/method by C#

// Create MRubyState object.
var state = MRubyState.Create();

// Define class
var classA = state.DefineClass(Intern("A"u8), c =>
{
    // Method definition that takes a required argument.
    c.DefineMethod(Intern("plus100"u8), (state, self) =>
    {
        var arg0 = state.GetArgAsIntegeger(0); // get first argument (index:0)
        return MRubyValue.From(arg0 + 100);
    });

    // Method definition that takes a block argument.
    c.DefineMethod(Intern("method2"), (state, self) =>
    {
        var arg0 = state.GetArg(0);
        var blockArg = state.GetBlockArg();
        if (!blockArg.IsNil)
        {
            // Execute `Proc#call`
            state.Send(blockArg, state.Intern("call"u8), arg0);
        }
    });
    
    // Other complex arguments...
    c.DefineMethod(Intern("method3"), (state, self) =>
    {
        var keywordArg = state.GetKeywordArg(state.Intern("foo"))
        Console.WriteLine($"foo: {keywordArg}");
        
        // argument type checking
        state.EnsureValueType(keywordArg, MrubyVType.Integer);
        
        var restArguments = state.GetRestArguments();
        for (var i = 0; i < restArguments.Length; i++)
        {
            Console.WriteLine($"rest arg({i}: {restArguments[i]})");
        }
    });
    
    // class method
    c.DefineClassMethod(Intern("classmethod1"), (state, self) => 
    {
        var str = state.NewString($"hoge fuga");
        return MRubyValue.From(str);
    });
    
});

// Monkey patching
classA.DefineMethod(Intern("additional_method1"u8), (state, self) => { /* ... */ });

// Define module
var moduleA = state.DefineModule(Intern("ModuleA");)
state.DefineMethod(moduleA, Intern("additional_method2"u8), (state, self) => MRubyValue.From(123));

state.IncludeModule(classA, moduleA);
a = A.new
a.plus100(123) #=> 223

a.method2(1) { |a| a } #=> 1

a.additionoa_method2 #=> 123

A.classmethod1 #=> "hoge fuga"

The string representation within mruby is utf8. Therefore, to generate a ruby string from C#, Utf8StringInterpolation is used internally.

// Create string literal.
var str1 = state.NewString("HOGE HOGE"u8);

// Create string via interpolation
var x = 123;
var str2 = state.NewString($"x={x}");

// wrap MRubyValue..
var strValue = MRubyValue.From(str1);

There is a concept in mruby similar to String called Symbol. Like String, it is created using utf8 strings, but internally it is a uint integer. Symbols are usually used for method IDs and class IDs.

To create a symbol from C#, use Intern.

// symbol literal
var sym1 = state.Intern("sym"u8)

// symbol from string
var sym2 = state.ToSymbol(state.NewString("sym2"u8));

MRubyD only includes the mruby virtual machine. Therefore it is necessary to convert it to .mrb bytecode before executing the .rb source. Basically, you need the native compiler provided by the mruby project.

$ git clone [email protected]:mruby/mruby.git
$ cd mruby
$ rake
$ ./build/host/bin/mrubc

To simplify compilation from C#, we also provide the MRubyD.Compiler package, which is a thin wrapper for the native compiler.

Note

This MRubyD.Compiler package is a thin wrapper for the native binary. Currently, builds for linux (x64/arm64), macOS (x64/arm64), and windows (x64) are provided.

dotnet add package MRubyD.Compiler
using MRubyD.Compiler;

var source = """
def a
  1
end

a
"""u8;

var state = MRubyState.Create();
var compiler = MRubyCompiler.Create(state);

var irep = compiler.Compile(source);

state.Exec(irep); // => 1

MIT

@hadahsiA

联系我们 contact @ memedata.com