Skip to content

Commit 31d0558

Browse files
committed
[GR-15673] Support access to local variables of the interactive Binding with Context#getBindings("ruby")
PullRequest: truffleruby/2331
2 parents 18d6bad + 076cbd8 commit 31d0558

File tree

9 files changed

+242
-61
lines changed

9 files changed

+242
-61
lines changed

src/main/java/org/truffleruby/core/CoreLibrary.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import org.truffleruby.core.rope.CodeRange;
4646
import org.truffleruby.core.string.RubyString;
4747
import org.truffleruby.core.string.StringOperations;
48+
import org.truffleruby.debug.BindingLocalVariablesObject;
4849
import org.truffleruby.debug.GlobalVariablesObject;
4950
import org.truffleruby.debug.TopScopeObject;
5051
import org.truffleruby.extra.ffi.Pointer;
@@ -216,6 +217,7 @@ public class CoreLibrary {
216217
public final RubyBasicObject mainObject;
217218

218219
public final GlobalVariables globalVariables;
220+
public final BindingLocalVariablesObject interactiveBindingLocalVariablesObject;
219221

220222
public final FrameDescriptor emptyDescriptor;
221223
/* Some things (such as procs created from symbols) require a declaration frame, and this should include a slot for
@@ -241,6 +243,7 @@ public class CoreLibrary {
241243
@CompilationFinal private GlobalVariableReader stderrReader;
242244

243245
@CompilationFinal public RubyBinding topLevelBinding;
246+
@CompilationFinal public RubyBinding interactiveBinding;
244247
@CompilationFinal public TopScopeObject topScopeObject;
245248

246249
private final ConcurrentMap<String, Boolean> patchFiles;
@@ -552,8 +555,12 @@ public CoreLibrary(RubyContext context, RubyLanguage language) {
552555
argv = new RubyArray(arrayClass, language.arrayShape, ArrayStoreLibrary.INITIAL_STORE, 0);
553556

554557
globalVariables = new GlobalVariables();
558+
interactiveBindingLocalVariablesObject = new BindingLocalVariablesObject();
555559
topScopeObject = new TopScopeObject(
556-
new Object[]{ new GlobalVariablesObject(globalVariables), mainObject });
560+
new Object[]{
561+
interactiveBindingLocalVariablesObject,
562+
new GlobalVariablesObject(globalVariables),
563+
mainObject });
557564

558565
patchFiles = initializePatching(context);
559566
}
@@ -829,6 +836,12 @@ private void afterLoadCoreLibrary() {
829836
topLevelBinding = (RubyBinding) objectClass.fields
830837
.getConstant("TOPLEVEL_BINDING")
831838
.getValue();
839+
840+
interactiveBinding = (RubyBinding) truffleBootModule.fields
841+
.getConstant("INTERACTIVE_BINDING")
842+
.getValue();
843+
844+
interactiveBindingLocalVariablesObject.setBinding(interactiveBinding);
832845
}
833846

834847
/** Convert a value to a {@code Float}, without doing any lookup. */

src/main/java/org/truffleruby/core/binding/BindingNodes.java

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
*/
1010
package org.truffleruby.core.binding;
1111

12+
import java.util.ArrayList;
1213
import java.util.LinkedHashSet;
14+
import java.util.List;
1315
import java.util.Set;
1416

1517
import com.oracle.truffle.api.dsl.CachedContext;
@@ -31,6 +33,7 @@
3133
import org.truffleruby.core.string.RubyString;
3234
import org.truffleruby.core.string.StringNodes.MakeStringNode;
3335
import org.truffleruby.language.Nil;
36+
import org.truffleruby.language.RubyBaseNode;
3437
import org.truffleruby.language.RubyNode;
3538
import org.truffleruby.language.RubySourceNode;
3639
import org.truffleruby.language.Visibility;
@@ -156,6 +159,42 @@ protected RubyBinding dup(RubyBinding binding) {
156159
}
157160
}
158161

162+
/** Same as {@link LocalVariableDefinedNode} but returns false instead of raising an exception for hidden
163+
* variables. */
164+
@ImportStatic({ BindingNodes.class, FindDeclarationVariableNodes.class })
165+
@GenerateUncached
166+
public abstract static class HasLocalVariableNode extends RubyBaseNode {
167+
168+
public static HasLocalVariableNode create() {
169+
return BindingNodesFactory.HasLocalVariableNodeGen.create();
170+
}
171+
172+
public abstract boolean execute(RubyBinding binding, String name);
173+
174+
@Specialization(
175+
guards = {
176+
"name == cachedName",
177+
"getFrameDescriptor(binding) == descriptor" },
178+
limit = "getCacheLimit()")
179+
protected boolean localVariableDefinedCached(RubyBinding binding, String name,
180+
@Cached("name") String cachedName,
181+
@Cached("getFrameDescriptor(binding)") FrameDescriptor descriptor,
182+
@Cached("findFrameSlotOrNull(name, binding.getFrame())") FrameSlotAndDepth cachedFrameSlot) {
183+
return cachedFrameSlot != null;
184+
}
185+
186+
@TruffleBoundary
187+
@Specialization(replaces = "localVariableDefinedCached")
188+
protected boolean localVariableDefinedUncached(RubyBinding binding, String name) {
189+
return FindDeclarationVariableNodes.findFrameSlotOrNull(name, binding.getFrame()) != null;
190+
}
191+
192+
protected int getCacheLimit() {
193+
return RubyLanguage.getCurrentLanguage().options.BINDING_LOCAL_VARIABLE_CACHE;
194+
}
195+
196+
}
197+
159198
@ImportStatic({ BindingNodes.class, FindDeclarationVariableNodes.class })
160199
@GenerateUncached
161200
@GenerateNodeFactory
@@ -164,10 +203,6 @@ protected RubyBinding dup(RubyBinding binding) {
164203
@NodeChild(value = "name", type = RubyNode.class)
165204
public abstract static class LocalVariableDefinedNode extends RubySourceNode {
166205

167-
public static LocalVariableDefinedNode create() {
168-
return BindingNodesFactory.LocalVariableDefinedNodeFactory.create(null, null);
169-
}
170-
171206
public abstract boolean execute(RubyBinding binding, String name);
172207

173208
@CreateCast("name")
@@ -362,34 +397,49 @@ public abstract static class LocalVariablesNode extends PrimitiveArrayArgumentsN
362397
limit = "getCacheLimit()")
363398
protected RubyArray localVariablesCached(RubyBinding binding,
364399
@Cached("getFrameDescriptor(binding)") FrameDescriptor cachedFrameDescriptor,
365-
@Cached("listLocalVariables(getContext(), binding.getFrame())") RubyArray names) {
400+
@Cached("listLocalVariablesAsSymbols(getContext(), binding.getFrame())") RubyArray names) {
366401
return names;
367402
}
368403

369404
@Specialization(replaces = "localVariablesCached")
370405
protected RubyArray localVariables(RubyBinding binding) {
371-
return listLocalVariables(getContext(), binding.getFrame());
406+
return listLocalVariablesAsSymbols(getContext(), binding.getFrame());
372407
}
373408

374409
@TruffleBoundary
375-
public RubyArray listLocalVariables(RubyContext context, MaterializedFrame frame) {
410+
public RubyArray listLocalVariablesAsSymbols(RubyContext context, MaterializedFrame frame) {
376411
final Set<Object> names = new LinkedHashSet<>();
377412
while (frame != null) {
378413
addNamesFromFrame(frame, names);
379-
380414
frame = RubyArguments.getDeclarationFrame(frame);
381415
}
382416
return ArrayHelpers.createArray(context, getLanguage(), names.toArray());
383417
}
384418

385-
private void addNamesFromFrame(Frame frame, final Set<Object> names) {
419+
private void addNamesFromFrame(Frame frame, Set<Object> names) {
386420
for (FrameSlot slot : frame.getFrameDescriptor().getSlots()) {
387421
if (!isHiddenVariable(slot.getIdentifier())) {
388422
names.add(getSymbol((String) slot.getIdentifier()));
389423
}
390424
}
391425
}
392426

427+
@TruffleBoundary
428+
public static List<String> listLocalVariablesWithDuplicates(MaterializedFrame frame) {
429+
List<String> members = new ArrayList<>();
430+
Frame currentFrame = frame;
431+
while (currentFrame != null) {
432+
final FrameDescriptor frameDescriptor = currentFrame.getFrameDescriptor();
433+
for (FrameSlot slot : frameDescriptor.getSlots()) {
434+
if (!isHiddenVariable(slot.getIdentifier())) {
435+
members.add(slot.getIdentifier().toString());
436+
}
437+
}
438+
currentFrame = RubyArguments.getDeclarationFrame(currentFrame);
439+
}
440+
return members;
441+
}
442+
393443
protected int getCacheLimit() {
394444
return getLanguage().options.BINDING_LOCAL_VARIABLE_CACHE;
395445
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. This
3+
* code is released under a tri EPL/GPL/LGPL license. You can use it,
4+
* redistribute it and/or modify it under the terms of the:
5+
*
6+
* Eclipse Public License version 2.0, or
7+
* GNU General Public License version 2, or
8+
* GNU Lesser General Public License version 2.1.
9+
*/
10+
package org.truffleruby.debug;
11+
12+
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
13+
import com.oracle.truffle.api.library.CachedLibrary;
14+
import org.truffleruby.core.binding.BindingNodes;
15+
import org.truffleruby.core.binding.RubyBinding;
16+
import org.truffleruby.core.string.StringUtils;
17+
import org.truffleruby.language.control.RaiseException;
18+
import org.truffleruby.parser.Identifiers;
19+
20+
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
21+
import com.oracle.truffle.api.dsl.Cached;
22+
import com.oracle.truffle.api.dsl.Cached.Exclusive;
23+
import com.oracle.truffle.api.interop.InteropLibrary;
24+
import com.oracle.truffle.api.interop.TruffleObject;
25+
import com.oracle.truffle.api.interop.UnknownIdentifierException;
26+
import com.oracle.truffle.api.library.ExportLibrary;
27+
import com.oracle.truffle.api.library.ExportMessage;
28+
29+
@ExportLibrary(InteropLibrary.class)
30+
public class BindingLocalVariablesObject implements TruffleObject {
31+
32+
@CompilationFinal private RubyBinding binding;
33+
34+
public BindingLocalVariablesObject() {
35+
}
36+
37+
public void setBinding(RubyBinding binding) {
38+
this.binding = binding;
39+
}
40+
41+
@ExportMessage
42+
protected boolean hasMembers() {
43+
return true;
44+
}
45+
46+
@ExportMessage
47+
@TruffleBoundary
48+
protected Object getMembers(boolean includeInternal) {
49+
String[] variables = BindingNodes.LocalVariablesNode
50+
// There should be no duplicates since there is no scope above
51+
.listLocalVariablesWithDuplicates(binding.getFrame())
52+
.toArray(StringUtils.EMPTY_STRING_ARRAY);
53+
return new VariableNamesObject(variables);
54+
}
55+
56+
@ExportMessage
57+
protected Object readMember(String member,
58+
@Cached @Exclusive BindingNodes.LocalVariableGetNode localVariableGetNode)
59+
throws UnknownIdentifierException {
60+
try {
61+
return localVariableGetNode.execute(binding, member);
62+
} catch (RaiseException e) {
63+
throw UnknownIdentifierException.create(member);
64+
}
65+
}
66+
67+
@ExportMessage
68+
protected void writeMember(String member, Object value,
69+
@Cached BindingNodes.LocalVariableSetNode localVariableSetNode) throws UnknownIdentifierException {
70+
if (isValidLocalVariableName(member)) {
71+
localVariableSetNode.execute(binding, member, value);
72+
} else {
73+
throw UnknownIdentifierException.create(member);
74+
}
75+
}
76+
77+
@ExportMessage(name = "isMemberReadable")
78+
@ExportMessage(name = "isMemberModifiable")
79+
protected boolean memberExists(String member,
80+
@Cached @Exclusive BindingNodes.HasLocalVariableNode hasLocalVariableNode) {
81+
return hasLocalVariableNode.execute(binding, member);
82+
}
83+
84+
@ExportMessage
85+
protected boolean isMemberInsertable(String member,
86+
@CachedLibrary("this") InteropLibrary interopLibrary) {
87+
return isValidLocalVariableName(member) && !interopLibrary.isMemberModifiable(this, member);
88+
}
89+
90+
private static boolean isValidLocalVariableName(String name) {
91+
return Identifiers.isValidLocalVariableName(name);
92+
}
93+
94+
}

src/main/java/org/truffleruby/debug/RubyScope.java

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@
1616
import com.oracle.truffle.api.dsl.CachedContext;
1717
import com.oracle.truffle.api.dsl.CachedLanguage;
1818
import com.oracle.truffle.api.dsl.Specialization;
19-
import com.oracle.truffle.api.frame.Frame;
20-
import com.oracle.truffle.api.frame.FrameDescriptor;
21-
import com.oracle.truffle.api.frame.FrameSlot;
2219
import com.oracle.truffle.api.frame.MaterializedFrame;
2320
import com.oracle.truffle.api.interop.InteropLibrary;
2421
import com.oracle.truffle.api.interop.TruffleObject;
@@ -38,7 +35,6 @@
3835
import org.truffleruby.language.arguments.RubyArguments;
3936
import org.truffleruby.language.control.RaiseException;
4037

41-
import java.util.ArrayList;
4238
import java.util.List;
4339

4440
@ExportLibrary(InteropLibrary.class)
@@ -144,17 +140,7 @@ protected static Object read(RubyScope scope, String member,
144140
@ExportMessage
145141
@TruffleBoundary
146142
protected Object getMembers(boolean includeInternal) {
147-
List<String> members = new ArrayList<>();
148-
Frame currentFrame = frame;
149-
while (currentFrame != null) {
150-
final FrameDescriptor frameDescriptor = currentFrame.getFrameDescriptor();
151-
for (FrameSlot slot : frameDescriptor.getSlots()) {
152-
if (!BindingNodes.isHiddenVariable(slot.getIdentifier())) {
153-
members.add(slot.getIdentifier().toString());
154-
}
155-
}
156-
currentFrame = RubyArguments.getDeclarationFrame(currentFrame);
157-
}
143+
List<String> members = BindingNodes.LocalVariablesNode.listLocalVariablesWithDuplicates(frame);
158144
members.add(RECEIVER_MEMBER);
159145
return new VariableNamesObject(members.toArray(StringUtils.EMPTY_STRING_ARRAY));
160146
}
@@ -168,8 +154,8 @@ protected static boolean readSelf(RubyScope scope, String member) {
168154

169155
@Specialization(guards = "!RECEIVER_MEMBER.equals(member)")
170156
protected static boolean isMemberReadable(RubyScope scope, String member,
171-
@Cached @Exclusive BindingNodes.LocalVariableDefinedNode localVariableDefinedNode) {
172-
return localVariableDefinedNode.execute(scope.binding, member);
157+
@Cached @Exclusive BindingNodes.HasLocalVariableNode hasLocalVariableNode) {
158+
return hasLocalVariableNode.execute(scope.binding, member);
173159
}
174160
}
175161

@@ -181,9 +167,9 @@ protected static boolean readSelf(RubyScope scope, String member) {
181167
}
182168

183169
@Specialization(guards = "!RECEIVER_MEMBER.equals(member)")
184-
protected static boolean isMemberReadable(RubyScope scope, String member,
185-
@Cached @Exclusive BindingNodes.LocalVariableDefinedNode localVariableDefinedNode) {
186-
return localVariableDefinedNode.execute(scope.binding, member);
170+
protected static boolean isMemberModifiable(RubyScope scope, String member,
171+
@Cached @Exclusive BindingNodes.HasLocalVariableNode hasLocalVariableNode) {
172+
return hasLocalVariableNode.execute(scope.binding, member);
187173
}
188174
}
189175

0 commit comments

Comments
 (0)