Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
f5e4c28
Moving the Java loading functionality into EpbLanguage
JaroslavTulach Jul 14, 2025
a4f6cfc
Introducing EpbParseInstrument to allow parsing of (non-public) EpbLa…
JaroslavTulach Jul 14, 2025
95b06c1
Only use background compilation flag when profiling, not debugging
JaroslavTulach Jul 14, 2025
842c8cb
Able to call OtherJvmClassLoader in the test
JaroslavTulach Jul 14, 2025
6297773
Can load guest Java classes via EpbLanguage
JaroslavTulach Jul 14, 2025
e58f852
Loading other JVM classes via EpbLanguage
JaroslavTulach Jul 14, 2025
9803717
Using the same interop messages for OtherJvm and Espresso
JaroslavTulach Jul 14, 2025
3ece0f5
jvm-interop is now needed by EpbLanguage
JaroslavTulach Jul 14, 2025
8ee0376
Running the JavaInteropTest in both: host and guest interop mode
JaroslavTulach Jul 14, 2025
99200bc
Avoiding context manipulation in the other JVM Java
JaroslavTulach Jul 14, 2025
31d154c
Simple test demonstrating differences between JVM and dual JVM interop
JaroslavTulach Jul 14, 2025
5dbfaeb
There is a difference in behavior of cached and uncached invokeMember…
JaroslavTulach Jul 14, 2025
479398c
EpbLanguage now needs jvm-interop
JaroslavTulach Jul 15, 2025
1c279e6
Consistency of cached and uncached version of InteropLibrary.invokeMe…
JaroslavTulach Jul 15, 2025
b1815b9
Merge remote-tracking branch 'origin/develop' into wip/jtulach/Channe…
JaroslavTulach Jul 15, 2025
d4d8fe6
Access ContextUtils via ctx()
JaroslavTulach Jul 15, 2025
cb16b85
language-server/test needs jvm-interop for EpbLanguage dependency
JaroslavTulach Jul 16, 2025
9a56c6e
Wrap HostJavaObject into OtherContextObject
JaroslavTulach Jul 16, 2025
0b8ed72
Make sure UnknownIdentifierException gets thru both JVMs
JaroslavTulach Jul 16, 2025
9fe14e4
Enum constants are also fields
JaroslavTulach Jul 16, 2025
f3a11b0
enum can be pesistable
JaroslavTulach Jul 16, 2025
609cd41
Yield expected IOException
JaroslavTulach Jul 16, 2025
335006e
No exit status as PanicException isn't exit exception
JaroslavTulach Jul 16, 2025
3dfd40d
Propagate Truffle exceptions as ThrowValue message
JaroslavTulach Jul 16, 2025
8764fcc
Support for persisting java.time types used by InteropLibrary
JaroslavTulach Jul 16, 2025
c157ae0
Rewriting polyglot docs
JaroslavTulach Jul 17, 2025
e0a2f99
Moving all class loading into EpbLanguage
JaroslavTulach Jul 17, 2025
e9ea72a
@TruffleBoundary on class loading
JaroslavTulach Jul 17, 2025
e7120b5
Enter context before createPolyglotJava
JaroslavTulach Jul 17, 2025
532f9f6
Dedicated class for dealing with polyglot interop in EnsoContext
JaroslavTulach Jul 17, 2025
cad47c5
Supply LibraryResolver via InteropLibrary
JaroslavTulach Jul 17, 2025
d2abc79
Enso can catch Java NumberFormatException
JaroslavTulach Jul 17, 2025
2a59711
Propages Panic to Java
JaroslavTulach Jul 17, 2025
71c8f54
Don't delegate getSourceLocation() messages
JaroslavTulach Jul 17, 2025
c03042b
Explicitly rethrow the exception
JaroslavTulach Jul 18, 2025
861111d
GuestJavaInteropTest.testInterfaceProxyFailuresA properly propagates …
JaroslavTulach Jul 18, 2025
67d8c11
Using context with HostAccess.ALL to load other JVM classes
JaroslavTulach Jul 18, 2025
3ec303f
Adjusting documentation to comments of @otulach
JaroslavTulach Jul 18, 2025
e2f7596
Reverting changes in .dev-env
JaroslavTulach Jul 18, 2025
a95c74a
Documenting the EpbLanguage
JaroslavTulach Jul 18, 2025
63f1b12
Associate each side of jvm.Channel with own onEnter and onLeave funct…
JaroslavTulach Jul 19, 2025
5efc834
Documenting JavaInteropTest
JaroslavTulach Jul 19, 2025
62aaafe
Throw and catch TestException
JaroslavTulach Jul 20, 2025
0beaea8
Unwrap OtherJvmTruffleException delegate before passing it back
JaroslavTulach Jul 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 18 additions & 12 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2130,6 +2130,8 @@ lazy val `language-server` = (project in file("engine/language-server"))
(`language-server-deps-wrapper` / Compile / exportedModule).value,
(`fansi-wrapper` / Compile / exportedModule).value,
(`text-buffer` / Compile / exportedModule).value,
(`jvm-channel` / Compile / exportedModule).value,
(`jvm-interop` / Compile / exportedModule).value,
(`runtime-suggestions` / Compile / exportedModule).value,
(`runtime-parser` / Compile / exportedModule).value,
(`runtime-compiler` / Compile / exportedModule).value,
Expand Down Expand Up @@ -2414,10 +2416,13 @@ lazy val `runtime-language-epb` =
"org.graalvm.sdk" % "nativeimage" % graalMavenPackagesVersion
),
Compile / internalModuleDependencies := Seq(
(`jvm-channel` / Compile / exportedModule).value,
(`jvm-interop` / Compile / exportedModule).value,
(`ydoc-polyfill` / Compile / exportedModule).value,
(`runtime-utils` / Compile / exportedModule).value
)
)
.dependsOn(`jvm-interop` % Test)

lazy val `runtime-language-arrow` =
(project in file("engine/runtime-language-arrow"))
Expand Down Expand Up @@ -2696,6 +2701,8 @@ lazy val `runtime-integration-tests` =
(`connected-lock-manager` / Compile / exportedModule).value,
(`library-manager` / Compile / exportedModule).value,
(`persistance` / Compile / exportedModule).value,
(`jvm-channel` / Compile / exportedModule).value,
(`jvm-interop` / Compile / exportedModule).value,
(`interpreter-dsl` / Compile / exportedModule).value,
(`engine-common` / Compile / exportedModule).value,
(`edition-updater` / Compile / exportedModule).value,
Expand Down Expand Up @@ -2882,6 +2889,8 @@ lazy val `runtime-benchmarks` =
(`library-manager` / Compile / exportedModule).value,
(`persistance` / Compile / exportedModule).value,
(`interpreter-dsl` / Compile / exportedModule).value,
(`jvm-channel` / Compile / exportedModule).value,
(`jvm-interop` / Compile / exportedModule).value,
(`engine-common` / Compile / exportedModule).value,
(`edition-updater` / Compile / exportedModule).value,
(`editions` / Compile / exportedModule).value,
Expand Down Expand Up @@ -3990,22 +3999,19 @@ lazy val `jvm-interop` =
(Test / fork) := true,
commands += WithDebugCommand.withDebug,
libraryDependencies ++= Seq(
"org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % "provided",
"org.graalvm.truffle" % "truffle-dsl-processor" % graalMavenPackagesVersion % "provided",
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion % "provided",
"org.graalvm.sdk" % "graal-sdk" % graalMavenPackagesVersion % Test,
"junit" % "junit" % junitVersion % Test,
"com.github.sbt" % "junit-interface" % junitIfVersion % Test
"org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % "provided",
"org.graalvm.truffle" % "truffle-dsl-processor" % graalMavenPackagesVersion % "provided",
"org.graalvm.sdk" % "graal-sdk" % graalMavenPackagesVersion % Test,
"junit" % "junit" % junitVersion % Test,
"com.github.sbt" % "junit-interface" % junitIfVersion % Test
),
Compile / moduleDependencies ++= Seq(
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion,
"org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion,
"org.graalvm.sdk" % "nativeimage" % graalMavenPackagesVersion,
"org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion,
"org.graalvm.sdk" % "word" % graalMavenPackagesVersion
"org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion,
"org.graalvm.sdk" % "nativeimage" % graalMavenPackagesVersion,
"org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion,
"org.graalvm.sdk" % "word" % graalMavenPackagesVersion
),
Compile / internalModuleDependencies ++= Seq(
(`engine-common` / Compile / exportedModule).value,
(`jvm-channel` / Compile / exportedModule).value,
(`persistance` / Compile / exportedModule).value
)
Expand Down
45 changes: 35 additions & 10 deletions docs/polyglot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,46 @@ order: 0

# Enso Polyglot Support

Enso supports robust polyglot interoperation with other programming languages
that are supported on its platform. This section of the design documentation
deals with
Enso supports robust interoperability with other programming languages - e.g.
Enso is a _polyglot programming_ languge.

It is broken down into the following sections:
### interoperability with Java

Unlike many other programming languages the _system language_ of Enso (e.g. the
language that is used to do low-level operating system tasks) is **Java**. As
such a lot of attention has been dedicated to make interoperability with
**Java** as smooth as possible:

- [**Java:**](./java.md) Detailed info about the Java polyglot bindings.

Many `Standard` libraries are using these `polyglot java import` statements.
Custom projects and libraries are encouraged to do the same. Interoperability
with Java is a first class citizen in the Enso programming language.

### Interoperability with Python, JavaScript & co.

Enso greatly benefits from the
[polyglot ecosystem of GraalVM](http://graalvm.org) and easily interops with any
language from that ecosystem. Including **JavaScript**, **Python**, **R**, etc.

- [**Python:**](./python.md) Specifics of the Python polyglot bindings.

Interop with these _dynamic languages_ is primarily supported via
[foreign function definitions](./polyglot-bindings.md#foreign-functions). When
accessing Python, as well as other dynamic languages, the same pattern is used.
Enough include the language support in the
[distribution](../distribution/distribution.md) and the language gets
automatically exposed via `foreign` directive to Enso programs.

## Implementation

Additional overview is provided in following documents:

- [**Polyglot Bindings:**](./polyglot-bindings.md) A document providing an
overview of the mechanisms provided to work with polyglot bindings in Enso.
- [**Typing Polyglot Bindings:**](./typing-polyglot-bindings.md) An exploration
of how we can provide a modicum of type safety for the polyglot bindings in
Enso.

It also provides language-specific documentation for the various supported
polyglot languages. These are as follows:

- [**Java:**](./java.md) Information specific to the Java polyglot bindings.
- [**Python:**](./python.md) Information specific to the Python polyglot
bindings.
Implementation details are described in
[EpbLanguage javadoc](../../engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/EpbLanguage.java).
206 changes: 73 additions & 133 deletions docs/polyglot/polyglot-bindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,98 +8,19 @@ order: 1

# Polyglot Bindings

This document deals with the specification and design for the polyglot interop
system provided in the Enso runtime. This system allows users to connect Enso to
other supported programming languages, to both provide access to a wealth of
libraries, and to integrate Enso into existing systems.

The polyglot support in Enso is best-in class, and it supports this through two
main mechanisms:

1. **Polyglot FFI:** The low-level polyglot support provides a fairly low-level
syntax sugar system for working with values from foreign languages.
2. **Embedded Syntax:** This system allows users to write code from other
languages directly in their `.enso` files, and to seamlessly share values
between Enso and that foreign code.

<!-- MarkdownTOC levels="2,3" autolink="true" -->

- [Impedance Mismatch](#impedance-mismatch)
- [The Polyglot FFI](#the-polyglot-ffi)
- [Importing Polyglot Bindings](#importing-polyglot-bindings)
- [Using Polyglot Bindings](#using-polyglot-bindings)
- [Importing Polyglot Bindings \(Syntax\)](#importing-polyglot-bindings-syntax)
- [Using Polyglot Bindings \(Syntax\)](#using-polyglot-bindings-syntax)
- [Finding Polyglot Bindings](#finding-polyglot-bindings)
- [Embedded Syntax](#embedded-syntax)
- [Embedded Syntax Usage \(Syntax\)](#embedded-syntax-usage-syntax)

<!-- /MarkdownTOC -->
Enso provides [robust interoperability](./README.md) with other programming
languages. This document describes how users can connect Enso to other supported
programming languages to gain access to a wealth of libraries, as well as to
integrate Enso into existing systems.

## Impedance Mismatch

Polyglot interoperation in Enso has a significant impedance mismatch. In
essence, this means that there is a mismatch between Enso's language semantics
and the semantics of the foreign languages that are being worked with.

While some of this mismatch can be worked around by manually wrapping the
foreign constructs in Enso, there are still concepts that can't easily be
represented by Enso.

> The actionables for this section are:
>
> - Expand on the impedance mismatch and how it leads to the defined semantics.

## The Polyglot FFI

The low-level polyglot FFI mechanism refers to a way to use polyglot objects
directly in Enso code. This can be used to underlie a library implementaion in
Enso, or to interoperate with code running in other languages.

The mechanism provides users with the facilities to import bindings from other
languages and call them via a generic mechanism.

### Importing Polyglot Bindings

When importing a polyglot binding into scope in an Enso file, this introduces a
_polyglot object_ into scope. This object will have appropriate fields and/or
methods defined on it, as described by the foreign language implementation.

> The actionables for this section are:
>
> - Expand greatly on the detail of this as the semantics of the imports become
> clearer.

### Using Polyglot Bindings

With a polyglot object in scope, the user is free to call methods on it
directly. These polyglot objects are inherently dynamically typed, meaning that
any operation may _fail_ at runtime.

Enso implements a generic variadic syntax for calling polyglot functions using
vectors of arguments. In essence, this is necessary due to the significant
impedance mismatch between Enso's runtime semantics (let alone the type system)
and the runtime semantics of many of the polyglot languages.

We went the way of the variadic call for multiple reasons:

- It allows us to match up with a wide range of language semantics (such as
subtyping and overloading).
- It is flexible and easy to expand in the future.
- We can easily build a more Enso-feeling interface on top of it.

By way of illustrative example, Java supports method overloading and subtyping,
two things which have no real equivalent in the Enso type system.
## `polyglot import`

> The actionables for this section are:
>
> - Expand greatly on the runtime semantics of working with polyglot bindings.
> - Determine how to make the inherent 'failability' of polyglot objects safer.
Accessing existing objects of foreign languages can be done via
`polyglot xyz import` statements. This primarily works for **Java** classes:

### Importing Polyglot Bindings (Syntax)
- [**Java:**](./java.md) Detailed info about the Java polyglot bindings.

Polyglot bindings can be imported using a polyglot import directive. This is
constructed as follows:
The _polyglot import directive_ is constructed as follows:

- The `polyglot` keyword
- A language identifier (e.g. `java`).
Expand All @@ -113,39 +34,41 @@ For example:

```ruby
polyglot java import org.example.MyClass as MyClassJava
polyglot c import struct NetworkPacket as NetworkPacketC
polyglot c import struct NetworkPacket
```

### Using Polyglot Bindings (Syntax)

A polyglot binding is a polyglot object that has methods and/or fields defined
on it. Due to an impedance mismatch between languages, Enso implements a
variadic syntax for calling these polyglot bindings using vectors.

In essence, we have a primitive function as follows:
Once imported the `MyClassJava` as well as `NetworkPacket` objects behave as
`Any` Enso objects. Such objects have methods and/or fields defined on them. The
following is a valid usage of a polyglot binding:

```ruby
Polyglot.method : Polyglot.Object -> [Any] -> Any
main =
x = MyClassJava.foo [1, 2, 3] # a static method
inst = MyClassJava.new [a, b, c] # a constructor
bar = inst.method [x, y] # an instance method
```

It works as follows:
### Using Polyglot Bindings

- It is a method called `method` defined on the `Polyglot` type. The name
`method` is, however, a stand-in for the name of the method in question.
- It takes an object instance of the polyglot object.
- It takes a vector of arguments (and is hence variadic).
- And it returns some value.
With a polyglot object in scope, the user is free to call methods on it
directly. These polyglot objects are inherently dynamically typed, meaning that
they have `Any` type. As such there is no _static type checking_ when invoking
methods on such types and potential errors are only detected during runtime and
result in a runtime _failure_ (a typical behavior of Python or JavaScript
programs).

By way of example, the following is a valid usage of a polyglot binding:
Enso implements a generic variadic syntax for calling polyglot functions using
vectors of arguments. In essence, this is necessary due to the significant
impedance mismatch between Enso's runtime semantics and the runtime semantics of
many of the polyglot languages. Such a solution:

```ruby
polyglot java import com.example.MyClass as MyClassJava
- allows Enso to match up with a wide range of language semantics
- for example Java's subtyping and overloading
- it is flexible and easy to expand in the future.
- allows building a more Enso-feeling interface on top of it.

main =
x = MyClassJava.foo [1, 2, 3] # a static method
inst = MyClassJava.new [a, b, c] # a constructor
bar = inst.metod [x, y] # a static method
```
Thanks to the generic variadic syntax, it is possible to smoothly invoke Java
overloaded and overriden methods.

### Finding Polyglot Bindings

Expand All @@ -158,32 +81,49 @@ Inside each directory is an implementation-defined structure, with the polyglot
implementation for that particular language needing to specify it. Please see
the language-specific documentation for details.

## Embedded Syntax
## `foreign` functions

It is possible to define new code snippets of foreign languages directly in
`.enso` source files using _"Embedded Syntax"_. Such a handy support provides a
truly smooth user experience:

The term "Embedded Syntax" is our terminology for the ability to use foreign
language syntaxes directly from inside `.enso` files. This system builds upon
the more generic mechanisms used by the [polyglot FFI](#the-polyglot-ffi) to
provide a truly seamless user experience.
```ruby
foreign python concat x y = """
def join(a, b):
return str(a) + str(b)
return join(x, y)

### Embedded Syntax Usage (Syntax)
main u="Hello" s=" " w="World!" =
concat (concat u s) w
```

A polyglot block is introduced as follows:
The previous example defines an Enso function `concat` that takes two arguments
`a` and `b`. The function is implemented in Python. The Python code defines a
local function `join` and uses it to compute and return result of `concat`. Then
the `concat` function is invoked from a `main` Enso function to concatenate
typical _Hello World!_ message.

- The `foreign` keyword starts a block.
- This must be followed by a language identifier (e.g. `python`).
- After the language identifier, the remaining syntax behaves like it is an Enso
function definition until the `=`.
- After the `=`, the user may write their foreign code as a string.
- [**Python:**](./python.md) Details on Python polyglot bindings.

```ruby
foreign python concat a b = """
def concat(a, b):
str(a) + str(b)
```
Similar syntax can be used for `js` and other dynamic languages. Certain
languages require/have special support, but in general this mechanism is reusing
polyglot capabilities of GraalVM Truffle framework and works with any language
that implements its `InteropLibrary` and _"parse in a context"_ protocols.

In the above example, this defines a function `concat` that takes two arguments
`a` and `b`, implemented in Python.
## Impedance Mismatch

> The actionables for this section are:
>
> - Greatly flesh out the syntax for the high-level polyglot functionality.
Enso is designed as a functional programming language and as such it assumes
_mininal side effects_ when performing operation. Especially the _live
programming_ environment provided by the Enso visual editor relies on operations
being idempotent and having no side effects. Enso semantic enforces such _no
side effects_ behavior for programs written in Enso.

This is not a typical behavior of other programming languages and certainly it
is not enforced in languages like JavaScript, Python or Java. Polyglot
interoperation in Enso has a significant impedance mismatch. In essence, this
means that there is a mismatch between Enso's language semantics and the
semantics of the foreign languages that are being worked with.

Some of thes mismatches can be worked around by manually wrapping the foreign
constructs in Enso, however some just cannot. Care must be taken when dealing
with other languages and especially their side-effects.
Loading
Loading