Skip to content

Commit 8a3ad67

Browse files
committed
Interactive sources should share the same binding
1 parent 7f54115 commit 8a3ad67

File tree

6 files changed

+68
-6
lines changed

6 files changed

+68
-6
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ Compatibility:
1212
* Added missing `Enumerable#filter` and `Enumerator::Lazy#filter` aliases to the respective `select` method (#1610).
1313
* Implement more `Ripper` methods as no-ops (#1694).
1414

15+
Changes:
16+
17+
* Interactive sources (like the GraalVM polyglot shell) now all share the same binding (#1695).
18+
1519
# 20.0.0 beta 1
1620

1721
Bug fixes:

doc/user/polyglot.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ If you are using the native configuration, you will need to use the `--polyglot`
1212
flag to get access to other languages. The JVM configuration automatically has
1313
access to other languages.
1414

15+
* [Running Ruby code from another language](#running-ruby-code-from-another-language)
1516
* [Loading code written in foreign languages](#loading-code-written-in-foreign-languages)
1617
* [Exporting Ruby objects to foreign languages](#exporting-ruby-objects-to-foreign-languages)
1718
* [Importing foreign objects to Ruby](#importing-foreign-objects-to-ruby)
@@ -22,6 +23,20 @@ access to other languages.
2223
* [Threading and interop](#threading-and-interop)
2324
* [Embedded configuration](#embedded-configuration)
2425

26+
## Running Ruby code from another language
27+
28+
When you `eval` Ruby code from the Polyglot API in another language and mark
29+
the source as interactive, the same interactive top-level binding is used each
30+
time. This means that if you set a local variable in one `eval`, you will be
31+
able to use it from the next.
32+
33+
Not that parsing Ruby is dependent on the current state of the top-level
34+
binding. If you parse an interactive source with the binding at one point in
35+
time, and then execute against the binding after it has been modified, the
36+
code will not be re-parsed with the modified binding. You may even find that
37+
you create a race condition, or parse against a binding in a state during the
38+
execution of another source.
39+
2540
## Loading code written in foreign languages
2641

2742
`Polyglot.eval(id, string)` executes code in a foreign language identified by

src/main/java/org/truffleruby/language/RubyParsingRequestNode.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
import com.oracle.truffle.api.RootCallTarget;
1616
import com.oracle.truffle.api.Truffle;
1717
import com.oracle.truffle.api.TruffleLanguage;
18+
import com.oracle.truffle.api.frame.MaterializedFrame;
1819
import com.oracle.truffle.api.frame.VirtualFrame;
1920
import com.oracle.truffle.api.nodes.DirectCallNode;
2021
import com.oracle.truffle.api.object.DynamicObject;
2122
import com.oracle.truffle.api.source.Source;
23+
import org.truffleruby.Layouts;
2224
import org.truffleruby.RubyContext;
2325
import org.truffleruby.RubyLanguage;
2426
import org.truffleruby.language.arguments.RubyArguments;
@@ -41,6 +43,7 @@ public class RubyParsingRequestNode extends RubyBaseRootNode implements Internal
4143
@CompilationFinal private RubyContext cachedContext;
4244
@CompilationFinal private DynamicObject mainObject;
4345
@CompilationFinal private InternalMethod method;
46+
@CompilationFinal private MaterializedFrame declarationFrame;
4447

4548
@Child private DirectCallNode callNode;
4649

@@ -66,12 +69,20 @@ public Object execute(VirtualFrame frame) {
6669

6770
final TranslatorDriver translator = new TranslatorDriver(context);
6871

72+
final boolean interactive = source.isInteractive();
73+
74+
if (interactive) {
75+
declarationFrame = Layouts.BINDING.getFrame(
76+
(DynamicObject) Layouts.MODULE.getFields(context.getCoreLibrary().getTruffleBootModule())
77+
.getConstant("INTERACTIVE_BINDING").getValue());
78+
}
79+
6980
final RubyRootNode rootNode = translator.parse(
7081
new RubySource(source),
71-
ParserContext.TOP_LEVEL,
82+
interactive ? ParserContext.EVAL : ParserContext.TOP_LEVEL,
7283
argumentNames,
73-
null,
74-
true,
84+
declarationFrame,
85+
!interactive,
7586
null);
7687

7788
final RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(rootNode);
@@ -87,7 +98,7 @@ public Object execute(VirtualFrame frame) {
8798
}
8899

89100
final Object value = callNode.call(RubyArguments.pack(
90-
null,
101+
declarationFrame,
91102
null,
92103
method,
93104
null,

src/main/java/org/truffleruby/parser/TranslatorDriver.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ public TranslatorDriver(RubyContext context) {
9696

9797
public RubyRootNode parse(RubySource rubySource, ParserContext parserContext, String[] argumentNames,
9898
MaterializedFrame parentFrame, boolean ownScopeForAssignments, Node currentNode) {
99-
10099
assert parserContext.isTopLevel() == (parentFrame == null) : "A frame should be given iff the context is not toplevel: " + parserContext + " " + parentFrame;
101100

102101
final Source source = rubySource.getSource();

src/main/ruby/truffleruby/core/truffle/boot.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414

1515
module Truffle::Boot
1616

17+
def self.create_interactive_binding
18+
binding
19+
end
20+
21+
INTERACTIVE_BINDING = create_interactive_binding
22+
1723
def self.check_syntax(source_or_file)
1824
inner_check_syntax source_or_file
1925
STDOUT.puts 'Syntax OK'

src/test/java/org/truffleruby/PolyglotInteropTest.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. This
2+
* Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. This
33
* code is released under a tri EPL/GPL/LGPL license. You can use it,
44
* redistribute it and/or modify it under the terms of the:
55
*
@@ -10,6 +10,7 @@
1010
package org.truffleruby;
1111

1212
import org.graalvm.polyglot.Context;
13+
import org.graalvm.polyglot.Source;
1314
import org.graalvm.polyglot.Value;
1415
import org.junit.Test;
1516
import org.truffleruby.fixtures.FluidForce;
@@ -21,6 +22,8 @@
2122
import java.util.function.IntConsumer;
2223

2324
import static org.junit.Assert.assertEquals;
25+
import static org.junit.Assert.assertFalse;
26+
import static org.junit.Assert.assertTrue;
2427

2528
public class PolyglotInteropTest {
2629

@@ -147,4 +150,28 @@ public void testParseOnceRunMany() {
147150
}
148151
}
149152

153+
@Test
154+
public void testLocalVariablesNotSharedBetweenNonInteractiveEval() {
155+
try (Context polyglot = Context.newBuilder()
156+
.option(OptionsCatalog.HOME.getName(), System.getProperty("user.dir"))
157+
.allowAllAccess(true)
158+
.build()) {
159+
polyglot.eval("ruby", "a = 14");
160+
assertTrue(polyglot.eval("ruby", "defined?(a).nil?").asBoolean());
161+
}
162+
}
163+
164+
@Test
165+
public void testLocalVariablesSharedBetweenInteractiveEval() {
166+
try (Context polyglot = Context.newBuilder()
167+
.option(OptionsCatalog.HOME.getName(), System.getProperty("user.dir"))
168+
.allowAllAccess(true)
169+
.build()) {
170+
polyglot.eval(Source.newBuilder("ruby", "a = 14", "test").interactive(true).buildLiteral());
171+
assertFalse(polyglot.eval(Source.newBuilder("ruby", "defined?(a).nil?", "test").interactive(true).buildLiteral()).asBoolean());
172+
polyglot.eval(Source.newBuilder("ruby", "b = 2", "test").interactive(true).buildLiteral());
173+
assertEquals(16, polyglot.eval(Source.newBuilder("ruby", "a + b", "test").interactive(true).buildLiteral()).asInt());
174+
}
175+
}
176+
150177
}

0 commit comments

Comments
 (0)