Experimental feature in GraalVM

多语言编程

TruffleRuby 允许您与其他任何 Truffle 语言交互,以创建多语言程序——即用多种语言编写的程序。

本指南描述了如何加载用外语编写的代码、如何在语言之间导出和导入对象、如何从外语使用 Ruby 对象、如何从 Ruby 使用外语对象、如何加载 Java 类型以与 Java 交互,以及如何嵌入到 Java 中。

如果您使用的是原生配置,您将需要使用 --polyglot 标志来访问其他语言。JVM 配置会自动访问其他语言。

安装其他语言 #

要使用其他 GraalVM 语言,您需要 JVM 独立版。原生独立版不支持安装额外的语言。

请注意,rubyllvm 和宿主 Java 互操作无需额外安装即可使用。

然后,您可以使用 truffleruby-polyglot-get $LANGUAGE 安装其他语言,例如

truffleruby-polyglot-get js
truffleruby-polyglot-get python
truffleruby-polyglot-get wasm
truffleruby-polyglot-get java # for Java on Truffle (aka Espresso)

在 23.1 之前的 TruffleRuby 版本中,这是通过安装 GraalVM(例如通过 truffleruby+graalvm)并使用 gu install $LANGUAGE 来完成的。

从其他语言运行 Ruby 代码 #

当您从其他语言的 Context API eval Ruby 代码并将 Source 标记为交互式时,每次都使用相同的交互式顶层绑定。这意味着如果您在一个 eval 中设置了一个局部变量,您将能够在下一个 eval 中使用它。

其语义与 Ruby 语义相同,即对于每个带有交互式 SourceContext.eval() 调用,都调用 INTERACTIVE_BINDING.eval(code)。这与大多数 REPL 语义相似。

加载用外语编写的代码 #

请注意,Ruby 命令行需要传递 --polyglot 以启用对外语的访问。

Polyglot.eval(id, string) 执行由其 ID 标识的外语代码。

Polyglot.eval_file(id, path) 从文件执行外语代码,由其语言 ID 标识。

Polyglot.eval_file(path) 从文件执行外语代码,自动确定语言。

将 Ruby 对象导出到外语 #

Polyglot.export(name, value) 导出具有给定名称的值。

Polyglot.export_method(name) 导出在顶层对象中定义的方法。

将外语对象导入到 Ruby #

Polyglot.import(name) 导入并返回具有给定名称的值。

Polyglot.import_method(name) 导入一个值(应为 IS_EXECUTABLE),具有给定名称,并在顶层对象中定义它。

从外语使用 Ruby 对象 #

以 JavaScript 为例:左边的示例是 JavaScript,右边是它对 Ruby 对象采取的相应操作,以 Ruby 代码表示。

object[name/index] 如果对象有 [] 方法,则调用 object[name/index],如果名称以 @ 开头,则读取实例变量,否则返回一个带有名称的绑定方法。

object[name/index] = value 如果对象有 []= 方法,则调用 object[name/index] = value,如果名称以 @ 开头,则设置实例变量。

delete object.name 调用 object.delete(name)

delete object[name/index] 调用 object.delete(name)

object.length 调用 object.size

Object.keys(hash) 将哈希键作为字符串返回。

Object.keys(object) 将对象的方法作为函数返回,除非对象有 [] 方法,在这种情况下返回一个空数组。

object(args...) 调用一个 Ruby ProcMethodUnboundMethod 等。

object.name(args...) 调用 Ruby 对象上的方法。

new object(args...) 调用 object.new(args...)

对于 Ruby Array"length" in obj 返回 true

object == null 调用 object.nil?

关于创建用于外语的 Ruby 对象的注意事项 #

如果您想将 Ruby 对象传递给其他语言以进行字段的读写,一个好的传递对象通常是 Struct,因为它将同时具有 object.fooobject.foo = value 访问器供您从 Ruby 使用,并且它们也将响应 object['foo']object['foo'] = value,这意味着它们将适用于从其他语言发送读写消息。

从 Ruby 使用外语对象 #

object[name/index] 将从外语对象中读取成员。

object[name/index] = value 将向外语对象写入值。

object.delete(name/index) 将从外语对象中删除值。

object.size 将获取外语对象的大小或长度。

object.keys 将获取外语对象的成员数组。

object.call(*args) 将执行外语对象。

object.name(*args) 将在外语对象上调用名为 name 的方法。

object.new(*args) 将从外语对象创建新对象(如同它某种类的实例)。

object.respond_to?(:size) 将告诉您外语对象是否具有大小或长度。

object.nil? 将告诉您外语对象是否表示该语言中等同于 nullnil 的值。

object.respond_to?(:call) 将告诉您外语对象是否可执行。

object.respond_to?(:new) 将告诉您外语对象是否可用于创建新对象(如果它是一个类)。

Polyglot.as_enumerable(object) 将从外语对象创建 Ruby Enumerable,使用其大小或长度,并从中读取。

当预期为布尔值时(例如,在 if 条件中),外语值如果可能则转换为布尔值,否则视为 true。

捕获外语异常 #

外语异常由 Polyglot::ForeignException 类表示,因此可以使用 rescue Polyglot::ForeignException => e 捕获。Polyglot::ForeignException 类继承自 StandardError,因此可以使用 rescue => e 代替。

任何外语异常都具有以下祖先:

Java.type("java.lang.RuntimeException").new.class.ancestors
# => [Polyglot::ForeignException, Polyglot::ExceptionTrait, Polyglot::ObjectTrait, StandardError, Exception, Object, Kernel, BasicObject]

可以使用特定的外语异常类捕获,例如 rescue Java.type("java.lang.NumberFormatException") => e

访问 Java 对象 #

TruffleRuby 的 Java 互操作接口类似于 Nashorn JavaScript 实现的接口,也由 GraalVM 的 JavaScript 实现所实现。

在 JVM 模式(--jvm)下使用 Java 互操作性更容易。原生模式也支持 Java 互操作性,但需要更多设置。有关更多详细信息,请参阅此处

Java.type('name') 返回一个 Java 类型,给定一个名称,如 java.lang.Integerint[]。对于类型对象,.new 将创建一个实例,.foo 将调用静态方法 foo.FOO[:FOO] 将读取静态字段 FOO,等等。要访问 java.lang.Class 实例的方法,请使用 [:class],例如 MyClass[:class].getName。您还可以通过使用 [:static]java.lang.Class 实例转到 Java 类型。

要在封闭模块中导入 Java 类,请使用 MyClass = Java.type 'java.lang.MyClass'Java.import 'java.lang.MyClass'

嵌入到 Java 中 #

TruffleRuby 通过 Polyglot API 嵌入,该 API 是 GraalVM 的一部分。您需要使用 GraalVM 才能使用此 API。

import org.graalvm.polyglot.*;

class Embedding {
    public static void main(String[] args) {
        Context polyglot = Context.newBuilder().allowAllAccess(true).build();
        Value array = polyglot.eval("ruby", "[1,2,42,4]");
        int result = array.getArrayElement(2).asInt();
        System.out.println(result);
    }
}

从嵌入式 Java 中使用 Ruby 对象 #

当嵌入到 Java 中时,Ruby 对象由 Value 类表示。

访问数组 #

boolean hasArrayElements()
Value getArrayElement(long index)
void setArrayElement(long index, Object value)
boolean removeArrayElement(long index)
long getArraySize()

访问对象中的方法 #

boolean hasMembers()
boolean hasMember(String identifier)
Value getMember(String identifier)
Set<String> getMemberKeys
void putMember(String identifier, Object value
boolean removeMember(String identifier)

执行 Proc、Lambda 和方法 #

boolean canExecute()
Value execute(Object... arguments)
void executeVoid(Object... arguments)

实例化类 #

boolean canInstantiate() {
Value newInstance(Object... arguments)

访问原语 #

boolean isString()
String asString()
boolean isBoolean()
boolean asBoolean()
boolean isNumber()
boolean fitsInByte()
byte asByte()
boolean fitsInShort()
short asShort()
boolean fitsInInt()
int asInt()
boolean fitsInLong()
long asLong()
boolean fitsInDouble()
double asDouble()
boolean fitsInFloat()
float asFloat()
boolean isNull()

JRuby 迁移指南包含更多示例。

线程和互操作 #

Ruby 被设计为多线程语言,并且大部分生态系统都期望线程可用。这可能与不支持线程的其他 Truffle 语言不兼容,因此您可以使用选项 --single-threaded 禁用多线程的创建。除非使用 Ruby 启动器(作为下面描述的嵌入式配置的一部分),否则此选项默认启用。

当此选项启用时,timeout 模块将警告超时被忽略,并且信号处理器将警告已捕获信号但不会运行处理器,因为这两个功能都需要启动新线程。

嵌入式配置 #

当在 Ruby 启动器之外使用时——例如通过 polyglot 接口从其他语言的启动器使用,使用原生 polyglot 库嵌入,或通过 GraalVM SDK 嵌入到 Java 应用程序中——TruffleRuby 将自动配置为在另一个应用程序中更协同地工作。这包括不安装中断信号处理器,以及使用 Graal SDK 的 I/O 流等选项。它还会像上面描述的那样开启单线程模式。

当您明确执行在嵌入式环境中可能无法正常工作的事情时(例如安装自己的信号处理器),它也会发出警告。

即使在嵌入式环境中,也可以通过 embedded 选项(从其他启动器使用 --ruby.embedded=false,或从普通的 Java 应用程序使用 -Dpolyglot.ruby.embedded=false)将其关闭。

这是一个单独的选项,但在嵌入式配置中,您可能希望在 Context.Builder 中设置 allowNativeAccess(false),或者使用实验性的 --platform-native=false 选项,以禁用 NFI 用于内部功能。

此外,实验性选项 --cexts=false 可以禁用 C 扩展。

注意:与例如纯 JavaScript 不同,Ruby 不仅仅是一种自包含的表达式语言。它有一个庞大的核心库,包括低级 I/O、系统和原生内存例程,这些例程可能会干扰其他嵌入式上下文或宿主系统。

内部上下文 #

TruffleRuby 支持创建内部上下文,即多个隔离的执行/评估上下文(这在 GraalVM 语言中通常受支持)。从概念上讲,它类似于在同一进程中运行多个 Ruby 解释器。这也可以与其他语言一起使用,例如外部/默认上下文可能运行一些 Ruby 代码,而一些内部上下文运行 JavaScript 代码。这对于与不支持共享内存多线程的语言(如 JavaScript)进行互操作非常有用,因为这样可以为每个线程创建一个或多个内部上下文,并且外部上下文仍然可以使用多线程 Ruby。

来自内部上下文的对象可以传递给其他上下文,并且它们被视为外语对象。

Polyglot::InnerContext.new do |context|
  context.eval('ruby', "p Object.new") # prints #<Object:0xd8>
  p context.eval('ruby', "Object.new") # prints #<Polyglot::ForeignObject[Ruby] Object:0x131d576b>
end

这通过自动将离开其上下文的每个对象包装在代理中来实现,这意味着例如,如果在一个外语对象上调用方法,则该方法在该对象所属的上下文中执行。

联系我们