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 中設定區域變數,您將能夠從下一個變數中使用它。

語意與針對每個具有互動式 SourceContext.eval() 呼叫來呼叫 INTERACTIVE_BINDING.eval(code) 的 Ruby 語意相同。這與大多數 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...)

"length" in obj 會針對 Ruby Array 傳回 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。

救援其他例外狀況 #

可以使用 rescue Polyglot::ForeignException => erescue 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.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)

執行 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 啟動器之外使用時,例如透過多語介面從其他語言的啟動器、使用原生多語程式庫嵌入或透過 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

這是透過自動將每個離開其上下文的物件包裝在代理中來實現的,這表示例如,如果在外部物件上呼叫方法,則會在物件所屬的上下文中執行。

與我們聯繫