- 适用于 JDK 23 的 GraalVM(最新版本)
- 适用于 JDK 24 的 GraalVM(抢先体验版)
- 适用于 JDK 21 的 GraalVM
- 适用于 JDK 17 的 GraalVM
- 存档
- 开发版本
多语言编程
TruffleRuby 允许您与任何其他 Truffle 语言进行交互,以创建多语言程序,即用多种语言编写的程序。
本指南介绍如何加载用其他语言编写的代码,如何在语言之间导出和导入对象,如何在其他语言中使用 Ruby 对象,如何在 Ruby 中使用其他语言的对象,如何加载 Java 类型以与 Java 交互,以及如何嵌入到 Java 中。
如果您使用的是原生配置,则需要使用 `--polyglot` 标志才能访问其他语言。JVM 配置会自动访问其他语言。
- 从其他语言运行 Ruby 代码
- 加载用其他语言编写的代码
- 将 Ruby 对象导出到其他语言
- 将其他语言的对象导入到 Ruby
- 在其他语言中使用 Ruby 对象
- 在 Ruby 中使用其他语言的对象
- 访问 Java 对象
- 线程和交互
- 嵌入式配置
安装其他语言 #
要使用其他 GraalVM 语言,您需要 JVM 独立版本。原生独立版本不支持安装额外的语言。
请注意,ruby
、llvm
和主机 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 语义相同,即对每个带有交互式 `Source` 的 `Context.eval()` 调用,都会调用 `INTERACTIVE_BINDING.eval(code)`。这类似于大多数 REPL 语义。
加载用其他语言编写的代码 #
请注意,ruby 命令行需要传递 `--polyglot` 才能启用访问其他语言。
Polyglot.eval(id, string)
使用其 ID 标识的其他语言执行代码。
Polyglot.eval_file(id, path)
使用其语言 ID 标识的其他语言从文件中执行代码。
Polyglot.eval_file(path)
使用其语言 ID 标识的其他语言从文件中执行代码。
将 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)
将对象的 method 作为 function 返回,除非该对象具有 `[]` method,在这种情况下,它返回一个空数组。
object(args...)
调用 Ruby 的 `Proc`、`Method`、`UnboundMethod` 等。
object.name(args...)
在 Ruby 对象上调用一个名为 `name` 的 method。
new object(args...)
调用 `object.new(args...)`。
"length" in obj
对于 Ruby `Array` 返回 `true`。
object == null
调用 `object.nil?`。
关于为其他语言使用创建 Ruby 对象的说明 #
如果您想将 Ruby 对象传递给其他语言以读取和写入字段,那么传递的最佳对象通常是 `Struct`,因为它将同时具有 `object.foo` 和 `object.foo = value` 访问器供您从 Ruby 使用,它们还将响应 `object['foo']` 和 `object['foo'] = value`,这意味着它们将适用于从其他语言发送读取和写入消息。
在 Ruby 中使用其他语言的对象 #
object[name/index]
将从其他语言对象中读取成员。
object[name/index] = value
将值写入其他语言对象。
object.delete(name/index)
将从其他语言对象中删除一个值。
object.size
将获取其他语言对象的 size 或 length。
object.keys
将获取其他语言对象的成员数组。
object.call(*args)
将执行其他语言对象。
object.name(*args)
将在其他语言对象上调用一个名为 `name` 的 method。
object.new(*args)
将从其他语言对象中创建一个新对象(就好像它是一种类一样)。
object.respond_to?(:size)
将告诉您其他语言对象是否具有 size 或 length。
object.nil?
将告诉您其他语言对象是否表示该语言的 `null` 或 `nil` 等效项。
object.respond_to?(:call)
将告诉您其他语言对象是否可以执行。
object.respond_to?(:new)
将告诉您其他语言对象是否可用于创建新对象(如果它是一个类)。
Polyglot.as_enumerable(object)
将使用其他语言对象的 size 或 length,并从中读取,从其他语言对象创建 Ruby `Enumerable`。
在需要布尔值的地方(例如,在 `if` 条件中),其他语言值将尽可能转换为布尔值或被认为是真值。
救援其他语言异常 #
其他语言异常可以通过 `rescue Polyglot::ForeignException => e` 或 `rescue foreign_meta_object` 捕获。可以通过 `rescue Exception => e` 捕获任何异常(Ruby 或其他语言)。
这自然源于其他语言异常的祖先
Java.type("java.lang.RuntimeException").new.class.ancestors
# => [Polyglot::ForeignException, Polyglot::ExceptionTrait, Polyglot::ObjectTrait, Exception, Object, Kernel, BasicObject]
访问 Java 对象 #
TruffleRuby 的 Java 交互界面类似于 Nashorn JavaScript 实现的界面,GraalVM 的 JavaScript 实现也实现了该界面。
在 JVM 模式(`--jvm`)下使用 Java 交互性更容易。Java 交互性在原生模式下也受支持,但需要更多设置。有关更多详细信息,请参阅 此处。
Java.type('name')
返回一个 Java 类型,使用例如 `java.lang.Integer` 或 `int[]` 这样的名称。对于类型对象,`new` 将创建一个实例,`foo` 将调用静态方法 `foo`,`FOO` 或 `[:FOO]` 将读取静态字段 `FOO`,等等。要访问 `java.lang.Class` 实例的方法,请使用 `[:class]`,例如 `MyClass[:class].getName`。您还可以从 `java.lang.Class` 实例转到 Java 类型,方法是使用 `[:static]`。
要在包含的模块中导入 Java 类,请使用 `MyClass = Java.type 'java.lang.MyClass'` 或 `Java.import 'java.lang.MyClass'`。
嵌入到 Java 中 #
TruffleRuby 是通过 Polyglot API 嵌入的,Polyglot 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)
执行 Procs、Lambdas 和方法 #
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 启动器之外使用 TruffleRuby 时,例如通过多语言接口从其他语言的启动器使用、使用原生多语言库嵌入或通过 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
这是通过自动将每个离开其上下文的对象包装在一个代理中来实现的,这意味着例如如果在外部对象上调用方法,它将在对象所属的上下文中执行。