Replace VmObjectLike.iterateMemberValues() with VmObjectCursor (#1879)
Motivation:
- VmObjectLike.iterateMemberValues() is an internal iterator whose loop body is a Java lambda.
It suffers from the following limitations:
- `continue` and `break` cannot be used.
- `return` becomes an (awkward) replacement for `continue` and `break`.
It cannot be used to return from the enclosing method.
- Mutating variables requires hoisting them (MutableBoolean, etc.).
- Node.execute() calls are very slow because they are behind a Truffle boundary.
- Callers of iterateMemberValues() do not specify what and how they want to iterate.
This obscures the iteration code and makes it difficult (often impossible) to optimize.
Changes:
- Add interface VmObjectCursor, an external iterator that iterates over VmObject members.
Its design is inspired by org.graalvm.collections.MapCursor.
- Add VmObjectCursor implementations specialized for different members, VmObject subclasses, and cursor options.
- Replace VmObjectLike.iterateMemberValues() with the following methods that return VmObjectCursor:
- properties()
- properties(CursorOption)
- elements()
- elements(CursorOption)
- entries()
- entries(CursorOption)
- members
- members(CursorOption)
- Replace all usages of iterateMemberValues() with one of the above methods.
Before: object.iterateMembersValues((key, member, value) -> { if (member.isProp()) {...} });
After : for (var cursor = object.properties(); cursor.advance(); ) {...}
Result:
- Iteration code is much simpler and clearer.
- Iteration code that calls Node.execute() is much faster because it is no longer behind a Truffle boundary.
- Other iteration code is potentially faster and can be better optimized in the future.
Co-authored-by: translatenix <119817707+translatenix@users.noreply.github.com>
Co-authored-by: Jen Basch <jbasch94@gmail.com>
This commit is contained in:
58 files changed
+2519
-759
No files matched your search
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.pkl.core;
|
||||
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
@@ -195,7 +196,9 @@ public final class EvaluatorImpl implements Evaluator {
|
||||
(module) -> {
|
||||
var output = VmUtils.readModuleOutput(module);
|
||||
return VmUtils.readFilesProperty(
|
||||
output, (fileOutput) -> new FileOutputImpl(this, fileOutput));
|
||||
output,
|
||||
(fileOutput) -> new FileOutputImpl(this, fileOutput),
|
||||
IndirectCallNode.getUncached());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.NodeChild;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
@@ -34,6 +35,9 @@ public abstract class EqualNode extends ExpressionNode {
|
||||
super(sourceSection);
|
||||
}
|
||||
|
||||
/** When using this method, pass {@code null} in for {@code leftNode} and {@code rightNode}. */
|
||||
public abstract boolean executeWith(VirtualFrame frame, Object left, Object right);
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(String left, String right) {
|
||||
return left.equals(right);
|
||||
|
||||
+55
-20
@@ -23,22 +23,31 @@ import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.frame.FrameDescriptor;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.ExplodeLoop;
|
||||
import com.oracle.truffle.api.profiles.LoopConditionProfile;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.pkl.core.ast.ExpressionNode;
|
||||
import org.pkl.core.ast.internal.ReadCursorValueNode;
|
||||
import org.pkl.core.ast.internal.ReadCursorValueNodeGen;
|
||||
import org.pkl.core.ast.type.TypeNode;
|
||||
import org.pkl.core.ast.type.UnresolvedTypeNode;
|
||||
import org.pkl.core.runtime.*;
|
||||
import org.pkl.core.runtime.Iterators.TruffleIterator;
|
||||
import org.pkl.core.util.ArrayUtils;
|
||||
|
||||
public abstract class GeneratorForNode extends GeneratorMemberNode {
|
||||
private final FrameDescriptor generatorDescriptor;
|
||||
private final LoopConditionProfile loopConditionProfile = LoopConditionProfile.create();
|
||||
@Child private ExpressionNode iterableNode;
|
||||
@Child private @Nullable UnresolvedTypeNode unresolvedKeyTypeNode;
|
||||
@Child private @Nullable UnresolvedTypeNode unresolvedValueTypeNode;
|
||||
@Children private final GeneratorMemberNode[] childNodes;
|
||||
@Child private @Nullable TypeNode keyTypeNode;
|
||||
@Child private @Nullable TypeNode valueTypeNode;
|
||||
|
||||
@Child
|
||||
private ReadCursorValueNode readCursorValueNode = ReadCursorValueNodeGen.create(sourceSection);
|
||||
|
||||
private final int keySlot;
|
||||
private final int valueSlot;
|
||||
private final int[] slotsToCopy;
|
||||
@@ -101,23 +110,29 @@ public abstract class GeneratorForNode extends GeneratorMemberNode {
|
||||
|
||||
@Specialization
|
||||
protected void eval(VirtualFrame frame, Object parent, ObjectData data, VmList iterable) {
|
||||
long idx = 0;
|
||||
for (var element : iterable) {
|
||||
executeIteration(frame, parent, data, idx++, element);
|
||||
loopConditionProfile.profileCounted(iterable.getLength());
|
||||
for (var idx = 0L; loopConditionProfile.inject(idx < iterable.getLength()); idx++) {
|
||||
executeIteration(frame, parent, data, idx, iterable.get(idx));
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected void eval(VirtualFrame frame, Object parent, ObjectData data, VmMap iterable) {
|
||||
for (var entry : iterable) {
|
||||
loopConditionProfile.profileCounted(iterable.getLength());
|
||||
var iterator = new TruffleIterator<>(iterable);
|
||||
while (loopConditionProfile.inject(iterator.hasNext())) {
|
||||
var entry = iterator.next();
|
||||
executeIteration(frame, parent, data, VmUtils.getKey(entry), VmUtils.getValue(entry));
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected void eval(VirtualFrame frame, Object parent, ObjectData data, VmSet iterable) {
|
||||
long idx = 0;
|
||||
for (var element : iterable) {
|
||||
var idx = 0L;
|
||||
loopConditionProfile.profileCounted(iterable.getLength());
|
||||
var iterator = new TruffleIterator<>(iterable);
|
||||
while (loopConditionProfile.inject(iterator.hasNext())) {
|
||||
var element = iterator.next();
|
||||
executeIteration(frame, parent, data, idx++, element);
|
||||
}
|
||||
}
|
||||
@@ -125,16 +140,20 @@ public abstract class GeneratorForNode extends GeneratorMemberNode {
|
||||
@Specialization
|
||||
protected void eval(VirtualFrame frame, Object parent, ObjectData data, VmIntSeq iterable) {
|
||||
var length = iterable.getLength();
|
||||
for (long key = 0, value = iterable.start; key < length; key++, value += iterable.step) {
|
||||
loopConditionProfile.profileCounted(iterable.getLength());
|
||||
for (long key = 0, value = iterable.start;
|
||||
loopConditionProfile.inject(key < length);
|
||||
key++, value += iterable.step) {
|
||||
executeIteration(frame, parent, data, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected void eval(VirtualFrame frame, Object parent, ObjectData data, VmBytes iterable) {
|
||||
long idx = 0;
|
||||
for (var byt : iterable.getBytes()) {
|
||||
executeIteration(frame, parent, data, idx++, (long) byt);
|
||||
var bytes = iterable.getBytes();
|
||||
loopConditionProfile.profileCounted(bytes.length);
|
||||
for (var idx = 0; loopConditionProfile.inject(idx < bytes.length); idx++) {
|
||||
executeIteration(frame, parent, data, (long) idx, (long) bytes[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,16 +169,32 @@ public abstract class GeneratorForNode extends GeneratorMemberNode {
|
||||
}
|
||||
|
||||
private void doEvalObject(VirtualFrame frame, VmObject iterable, Object parent, ObjectData data) {
|
||||
var materializedFrame = frame.materialize();
|
||||
iterable.forceAndIterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
var convertedKey = member.isProp() ? key.toString() : key;
|
||||
// TODO: Executing iteration behind a Truffle boundary is bad for performance.
|
||||
// This and similar cases will be fixed in an upcoming PR that replaces method
|
||||
// `(forceAnd)iterateMemberValues` with cursor-based external iterators.
|
||||
executeIteration(materializedFrame, parent, data, convertedKey, value);
|
||||
return true;
|
||||
});
|
||||
// TODO: skip types for module objects?
|
||||
var cursor = iterable.members();
|
||||
if (cursor.getLength() == -1) {
|
||||
doEvalObjectProfiled(frame, parent, data, cursor);
|
||||
} else {
|
||||
doEvalObjectCounted(frame, parent, data, cursor);
|
||||
}
|
||||
}
|
||||
|
||||
private void doEvalObjectCounted(
|
||||
VirtualFrame frame, Object parent, ObjectData data, VmObjectCursor cursor) {
|
||||
loopConditionProfile.profileCounted(cursor.getLength());
|
||||
while (loopConditionProfile.inject(cursor.advance())) {
|
||||
var key = cursor.isProperty() ? cursor.keyToString() : cursor.key();
|
||||
var value = readCursorValueNode.execute(frame, cursor);
|
||||
executeIteration(frame, parent, data, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void doEvalObjectProfiled(
|
||||
VirtualFrame frame, Object parent, ObjectData data, VmObjectCursor cursor) {
|
||||
while (loopConditionProfile.profile(cursor.advance())) {
|
||||
var key = cursor.isProperty() ? cursor.keyToString() : cursor.key();
|
||||
var value = readCursorValueNode.execute(frame, cursor);
|
||||
executeIteration(frame, parent, data, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@ExplodeLoop
|
||||
|
||||
+60
-29
@@ -23,8 +23,11 @@ import com.oracle.truffle.api.dsl.Fallback;
|
||||
import com.oracle.truffle.api.dsl.ImportStatic;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.profiles.LoopConditionProfile;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.ast.ExpressionNode;
|
||||
import org.pkl.core.ast.internal.ReadCursorValueNode;
|
||||
import org.pkl.core.ast.internal.ReadCursorValueNodeGen;
|
||||
import org.pkl.core.ast.member.ObjectMember;
|
||||
import org.pkl.core.runtime.BaseModule;
|
||||
import org.pkl.core.runtime.Identifier;
|
||||
@@ -41,12 +44,18 @@ import org.pkl.core.runtime.VmMap;
|
||||
import org.pkl.core.runtime.VmMapping;
|
||||
import org.pkl.core.runtime.VmNull;
|
||||
import org.pkl.core.runtime.VmObject;
|
||||
import org.pkl.core.runtime.VmObjectCursor;
|
||||
import org.pkl.core.runtime.VmTyped;
|
||||
import org.pkl.core.runtime.VmUtils;
|
||||
|
||||
@ImportStatic(BaseModule.class)
|
||||
public abstract class GeneratorSpreadNode extends GeneratorMemberNode {
|
||||
private final LoopConditionProfile loopConditionProfile = LoopConditionProfile.create();
|
||||
@Child private ExpressionNode iterableNode;
|
||||
|
||||
@Child
|
||||
private ReadCursorValueNode readCursorValueNode = ReadCursorValueNodeGen.create(sourceSection);
|
||||
|
||||
private final boolean nullable;
|
||||
|
||||
public GeneratorSpreadNode(
|
||||
@@ -189,53 +198,73 @@ public abstract class GeneratorSpreadNode extends GeneratorMemberNode {
|
||||
}
|
||||
|
||||
protected void doEvalDynamic(VirtualFrame frame, ObjectData data, VmObject iterable) {
|
||||
var materializedFrame = frame.materialize();
|
||||
iterable.forceAndIterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
var cursor = iterable.members();
|
||||
var counted = isCounted(cursor);
|
||||
while (advance(cursor, counted)) {
|
||||
var member = cursor.member();
|
||||
var value = readCursorValueNode.execute(frame, cursor);
|
||||
if (member.isElement()) {
|
||||
data.addElement(materializedFrame, createMember(member, value), this);
|
||||
data.addElement(frame, createMember(member, value), this);
|
||||
} else {
|
||||
data.addMember(materializedFrame, key, createMember(member, value), this);
|
||||
data.addMember(frame, cursor.key(), createMember(member, value), this);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void doEvalMapping(VirtualFrame frame, ObjectData data, VmObject iterable) {
|
||||
var materializedFrame = frame.materialize();
|
||||
iterable.forceAndIterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
var cursor = iterable.members();
|
||||
var counted = isCounted(cursor);
|
||||
while (advance(cursor, counted)) {
|
||||
var member = cursor.member();
|
||||
if (member.isElement() || member.isProp()) {
|
||||
cannotHaveMember(BaseModule.getMappingClass(), member);
|
||||
}
|
||||
data.addMember(materializedFrame, key, createMember(member, value), this);
|
||||
return true;
|
||||
});
|
||||
data.addMember(
|
||||
frame,
|
||||
cursor.key(),
|
||||
createMember(member, readCursorValueNode.execute(frame, cursor)),
|
||||
this);
|
||||
}
|
||||
}
|
||||
|
||||
private void doEvalListing(VirtualFrame frame, ObjectData data, VmObject iterable) {
|
||||
var materializedFrame = frame.materialize();
|
||||
iterable.forceAndIterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
var cursor = iterable.members();
|
||||
var counted = isCounted(cursor);
|
||||
while (advance(cursor, counted)) {
|
||||
var member = cursor.member();
|
||||
if (member.isEntry() || member.isProp()) {
|
||||
cannotHaveMember(getListingClass(), member);
|
||||
}
|
||||
data.addElement(materializedFrame, createMember(member, value), this);
|
||||
return true;
|
||||
});
|
||||
data.addElement(
|
||||
frame, createMember(member, readCursorValueNode.execute(frame, cursor)), this);
|
||||
}
|
||||
}
|
||||
|
||||
private void doEvalTyped(VirtualFrame frame, VmClass clazz, ObjectData data, VmObject iterable) {
|
||||
var materializedFrame = frame.materialize();
|
||||
iterable.forceAndIterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
var cursor = iterable.members();
|
||||
var counted = isCounted(cursor);
|
||||
while (advance(cursor, counted)) {
|
||||
var member = cursor.member();
|
||||
if (member.isElement() || member.isEntry()) {
|
||||
cannotHaveMember(clazz, member);
|
||||
}
|
||||
checkIsValidTypedProperty(clazz, member);
|
||||
data.addProperty(materializedFrame, createMember(member, value), this);
|
||||
data.addProperty(
|
||||
frame, createMember(member, readCursorValueNode.execute(frame, cursor)), this);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCounted(VmObjectCursor cursor) {
|
||||
var length = cursor.getLength();
|
||||
if (length == -1) return false;
|
||||
loopConditionProfile.profileCounted(length);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private boolean advance(VmObjectCursor cursor, boolean counted) {
|
||||
return counted
|
||||
? loopConditionProfile.inject(cursor.advance())
|
||||
: loopConditionProfile.profile(cursor.advance());
|
||||
}
|
||||
|
||||
// handles both `List` and `Set`
|
||||
@@ -250,7 +279,7 @@ public abstract class GeneratorSpreadNode extends GeneratorMemberNode {
|
||||
.withProgramValue("Value", iterable)
|
||||
.build();
|
||||
}
|
||||
spreadIterable(frame, data, iterable);
|
||||
spreadIterable(frame, data, iterable, iterable.getLength());
|
||||
}
|
||||
|
||||
private void doEvalMap(VirtualFrame frame, VmClass parent, ObjectData data, VmMap iterable) {
|
||||
@@ -278,7 +307,7 @@ public abstract class GeneratorSpreadNode extends GeneratorMemberNode {
|
||||
.withProgramValue("Value", iterable)
|
||||
.build();
|
||||
}
|
||||
spreadIterable(frame, data, iterable);
|
||||
spreadIterable(frame, data, iterable, iterable.getLength());
|
||||
}
|
||||
|
||||
private void doEvalBytes(VirtualFrame frame, VmClass parent, ObjectData data, VmBytes iterable) {
|
||||
@@ -290,7 +319,7 @@ public abstract class GeneratorSpreadNode extends GeneratorMemberNode {
|
||||
.withProgramValue("Value", iterable)
|
||||
.build();
|
||||
}
|
||||
spreadIterable(frame, data, iterable);
|
||||
spreadIterable(frame, data, iterable, iterable.getLength());
|
||||
}
|
||||
|
||||
private void cannotHaveMember(VmClass clazz, ObjectMember member) {
|
||||
@@ -352,9 +381,11 @@ public abstract class GeneratorSpreadNode extends GeneratorMemberNode {
|
||||
return result;
|
||||
}
|
||||
|
||||
private void spreadIterable(VirtualFrame frame, ObjectData data, Iterable<?> iterable) {
|
||||
private void spreadIterable(
|
||||
VirtualFrame frame, ObjectData data, Iterable<?> iterable, long length) {
|
||||
loopConditionProfile.profileCounted(length);
|
||||
var iterator = new TruffleIterator<>(iterable);
|
||||
while (iterator.hasNext()) {
|
||||
while (loopConditionProfile.inject(iterator.hasNext())) {
|
||||
var elem = iterator.next();
|
||||
var member = VmUtils.createSyntheticObjectElement(String.valueOf(data.length()), elem);
|
||||
data.addElement(frame, member, this);
|
||||
|
||||
+1
-1
@@ -148,7 +148,7 @@ public abstract class ConstantEntriesLiteralNode extends SpecializedObjectLitera
|
||||
"checkIsValidListingAmendment()",
|
||||
"checkMaxListingMemberIndex(0)"
|
||||
})
|
||||
protected void evalListingClass(@SuppressWarnings("unused") VmClass parent) {
|
||||
protected Object evalListingClass(@SuppressWarnings("unused") VmClass parent) {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw exceptionBuilder().unreachableCode().build();
|
||||
}
|
||||
|
||||
+3
-2
@@ -34,8 +34,9 @@ import org.pkl.core.runtime.*;
|
||||
import org.pkl.core.util.EconomicMaps;
|
||||
|
||||
/**
|
||||
* Object literal that contains both elements and entries (and possibly properties). Example: `new
|
||||
* foo { "pigeon", [3] = "barn owl" }`
|
||||
* Object literal that contains both elements and entries (and possibly properties).
|
||||
*
|
||||
* <p>Example: {@code new foo { "pigeon"; [3] = "barn owl" } }
|
||||
*/
|
||||
@ImportStatic({BaseModule.class, VmUtils.class})
|
||||
public abstract class ElementsEntriesLiteralNode extends SpecializedObjectLiteralNode {
|
||||
|
||||
@@ -42,6 +42,7 @@ public abstract class ObjectLiteralNode extends ExpressionNode {
|
||||
protected final boolean isCustomThisScope;
|
||||
protected final @Nullable FrameDescriptor parametersDescriptor;
|
||||
@Children protected final UnresolvedTypeNode[] parameterTypes;
|
||||
@Child protected @Nullable AmendFunctionNode amendFunctionNode;
|
||||
|
||||
public ObjectLiteralNode(
|
||||
SourceSection sourceSection,
|
||||
|
||||
+5
-2
@@ -16,9 +16,11 @@
|
||||
package org.pkl.core.ast.expression.member;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.NodeChild;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.ast.ExpressionNode;
|
||||
import org.pkl.core.runtime.*;
|
||||
@@ -36,13 +38,14 @@ public abstract class InferParentWithinPropertyNode extends ExpressionNode {
|
||||
}
|
||||
|
||||
@Specialization(guards = "!owner.isPrototype()")
|
||||
protected Object evalTypedObject(VirtualFrame frame, VmTyped owner) {
|
||||
protected Object evalTypedObject(
|
||||
VirtualFrame frame, VmTyped owner, @Cached("create()") IndirectCallNode callNode) {
|
||||
if (isLocalProperty) {
|
||||
return getLocalPropertyDefaultValue(frame, owner);
|
||||
}
|
||||
|
||||
try {
|
||||
var result = VmUtils.readMemberOrNull(owner.getPrototype(), ownPropertyName, false);
|
||||
var result = VmUtils.readMemberOrNull(owner.getPrototype(), ownPropertyName, false, callNode);
|
||||
assert result != null : "every property has a default";
|
||||
return result;
|
||||
} catch (VmUndefinedValueException e) {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.core.ast.internal;
|
||||
|
||||
import com.oracle.truffle.api.dsl.Bind;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.pkl.core.ast.PklNode;
|
||||
import org.pkl.core.runtime.VmObjectCursor;
|
||||
|
||||
public abstract class ReadCursorValueNode extends PklNode {
|
||||
protected ReadCursorValueNode() {}
|
||||
|
||||
protected ReadCursorValueNode(SourceSection sourceSection) {
|
||||
super(sourceSection);
|
||||
}
|
||||
|
||||
public abstract Object execute(VirtualFrame frame, VmObjectCursor cursor);
|
||||
|
||||
@Specialization(guards = "cachedValue != null")
|
||||
protected Object evalCached(
|
||||
@SuppressWarnings("unused") VmObjectCursor cursor,
|
||||
@Bind("cursor.cachedValueOrNull()") Object cachedValue) {
|
||||
return cachedValue;
|
||||
}
|
||||
|
||||
@Specialization(replaces = "evalCached")
|
||||
protected Object eval(
|
||||
VmObjectCursor cursor,
|
||||
@Bind("cursor.cachedValueOrNull()") @SuppressWarnings("unused") @Nullable Object cachedValue,
|
||||
@Cached("create()") IndirectCallNode callNode) {
|
||||
if (cachedValue != null) {
|
||||
return cachedValue;
|
||||
}
|
||||
return cursor.value(callNode);
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,10 @@ public final class BaseModule extends StdLibModule {
|
||||
return AnyClass.instance;
|
||||
}
|
||||
|
||||
public static VmClass getObjectClass() {
|
||||
return ObjectClass.instance;
|
||||
}
|
||||
|
||||
public static VmClass getTypedClass() {
|
||||
return TypedClass.instance;
|
||||
}
|
||||
@@ -256,6 +260,10 @@ public final class BaseModule extends StdLibModule {
|
||||
static final VmClass instance = loadClass("Any");
|
||||
}
|
||||
|
||||
private static final class ObjectClass {
|
||||
static final VmClass instance = loadClass("Object");
|
||||
}
|
||||
|
||||
private static final class TypedClass {
|
||||
static final VmClass instance = loadClass("Typed");
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import static org.pkl.core.runtime.VmUtils.REPL_TEXT;
|
||||
import static org.pkl.core.runtime.VmUtils.REPL_TEXT_URI;
|
||||
|
||||
import com.oracle.truffle.api.frame.FrameDescriptor;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.URI;
|
||||
@@ -67,6 +68,7 @@ import org.pkl.core.ast.type.UnresolvedTypeNode;
|
||||
import org.pkl.core.externalreader.ExternalReaderProcessException;
|
||||
import org.pkl.core.module.ModuleKeys;
|
||||
import org.pkl.core.module.ResolvedModuleKey;
|
||||
import org.pkl.core.runtime.VmObjectCursor.CursorOption;
|
||||
import org.pkl.core.util.EconomicMaps;
|
||||
import org.pkl.core.util.GlobResolver;
|
||||
import org.pkl.core.util.GlobResolver.InvalidGlobPatternException;
|
||||
@@ -174,30 +176,27 @@ public final class CommandSpecParser {
|
||||
var subcommandNames = new HashMap<String, Object>();
|
||||
var subcommandsProperty = (VmObject) VmUtils.readMember(commandInfo, Identifier.SUBCOMMANDS);
|
||||
subcommandsProperty.force(false, false);
|
||||
subcommandsProperty.iterateAlreadyForcedMemberValues(
|
||||
(key, member, value) -> {
|
||||
// track the subcommand index at first
|
||||
// replaced by subcommand name in doParse if reading name succeeds
|
||||
path.push("subcommands[" + key + "]");
|
||||
var spec = doParse((VmTyped) value, path);
|
||||
var callNode = IndirectCallNode.getUncached();
|
||||
for (var cursor = subcommandsProperty.members(CursorOption.ALL_VALUES); cursor.advance(); ) {
|
||||
var value = (VmTyped) cursor.value(callNode);
|
||||
path.push("subcommands[" + cursor.keyToString() + "]");
|
||||
var spec = doParse(value, path);
|
||||
path.pop();
|
||||
var existingNameIndex = subcommandNames.get(spec.name());
|
||||
if (existingNameIndex != null) {
|
||||
throw exceptionBuilder()
|
||||
.withSourceSection(member.getSourceSection())
|
||||
.withSourceSection(cursor.member().getSourceSection())
|
||||
.evalError(
|
||||
"commandSubcommandConflict",
|
||||
VmUtils.readMember(commandInfo, Identifier.NAME),
|
||||
spec.name(),
|
||||
existingNameIndex,
|
||||
key)
|
||||
cursor.keyToString())
|
||||
.build();
|
||||
}
|
||||
subcommandNames.put(spec.name(), key);
|
||||
subcommandNames.put(spec.name(), cursor.key());
|
||||
subcommands.add(spec);
|
||||
return true;
|
||||
});
|
||||
|
||||
}
|
||||
return subcommands;
|
||||
}
|
||||
|
||||
@@ -1087,7 +1086,10 @@ public final class CommandSpecParser {
|
||||
// otherwise value is Listing<String> so will export to List<String>
|
||||
if (!(value instanceof VmListing vmListing)) throw PklBugException.unreachableCode();
|
||||
var result = new HashSet<String>(vmListing.getLength());
|
||||
vmListing.forceAndIterateMemberValues((key, member, val) -> result.add((String) val));
|
||||
var callNode = IndirectCallNode.getUncached();
|
||||
for (var cursor = vmListing.members(); cursor.advance(); ) {
|
||||
result.add((String) cursor.value(callNode));
|
||||
}
|
||||
return new Fixed(result);
|
||||
}
|
||||
|
||||
@@ -1259,7 +1261,7 @@ public final class CommandSpecParser {
|
||||
var output = VmUtils.readModuleOutput(evaluated);
|
||||
return new CommandSpec.Result(
|
||||
VmUtils.readBytesProperty(output).export(),
|
||||
VmUtils.readFilesProperty(output, makeFileOutput));
|
||||
VmUtils.readFilesProperty(output, makeFileOutput, IndirectCallNode.getUncached()));
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.pkl.core.ValueFormatter;
|
||||
import org.pkl.core.runtime.VmObjectCursor.CursorOption;
|
||||
import org.pkl.core.util.StringSimilarity;
|
||||
|
||||
public final class KeyLookupSuggestions {
|
||||
@@ -49,15 +50,13 @@ public final class KeyLookupSuggestions {
|
||||
public static List<Candidate> forObject(VmObjectLike object, String key) {
|
||||
var candidates = new ArrayList<Candidate>();
|
||||
|
||||
object.iterateMemberValues(
|
||||
(memberKey, member, value) -> {
|
||||
if (!(memberKey instanceof String stringKey)) return true;
|
||||
for (var cursor = object.entries(CursorOption.ANY_ORDER); cursor.advance(); ) {
|
||||
if (!(cursor.key() instanceof String stringKey)) continue;
|
||||
var similarity = STRING_SIMILARITY.similarity(stringKey, key);
|
||||
if (similarity >= SIMILARITY_THRESHOLD) {
|
||||
candidates.add(new Candidate(stringKey, similarity));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
candidates.sort(Comparator.naturalOrder());
|
||||
return candidates;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.pkl.core.runtime;
|
||||
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import java.io.IOException;
|
||||
@@ -40,8 +41,6 @@ import org.pkl.core.stdlib.base.PcfRenderer;
|
||||
import org.pkl.core.util.AnsiStringBuilder;
|
||||
import org.pkl.core.util.AnsiTheme;
|
||||
import org.pkl.core.util.EconomicMaps;
|
||||
import org.pkl.core.util.MutableBoolean;
|
||||
import org.pkl.core.util.MutableReference;
|
||||
|
||||
/** Runs test results examples and facts. */
|
||||
public final class TestRunner {
|
||||
@@ -84,38 +83,38 @@ public final class TestRunner {
|
||||
}
|
||||
|
||||
private TestSectionResults runFacts(VmTyped testModule) {
|
||||
var facts = VmUtils.readMember(testModule, Identifier.FACTS);
|
||||
var callNode = IndirectCallNode.getUncached();
|
||||
var facts = VmUtils.readMember(testModule, Identifier.FACTS, callNode);
|
||||
if (facts instanceof VmNull) return new TestSectionResults(TestSectionName.FACTS, List.of());
|
||||
|
||||
var testResults = new ArrayList<TestResult>();
|
||||
var factsMapping = (VmMapping) facts;
|
||||
factsMapping.forceAndIterateMemberValues(
|
||||
(groupKey, groupMember, groupValue) -> {
|
||||
var listing = (VmListing) groupValue;
|
||||
for (var cursor = factsMapping.entries(); cursor.advance(); ) {
|
||||
var groupKey = cursor.key();
|
||||
var listing = (VmListing) cursor.value(callNode);
|
||||
var name = String.valueOf(groupKey);
|
||||
var resultBuilder = new TestResult.Builder(name);
|
||||
listing.iterateMembers(
|
||||
(idx, member) -> {
|
||||
for (var lCursor = listing.elements(); lCursor.advance(); ) {
|
||||
var idx = lCursor.key();
|
||||
var member = lCursor.member();
|
||||
if (member.isLocalOrExternalOrHidden()) {
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
var factValue = VmUtils.readMember(listing, idx);
|
||||
var factValue = lCursor.value(callNode);
|
||||
if (factValue == Boolean.FALSE) {
|
||||
if (PowerAssertions.isEnabled() && member.getSourceSection().isAvailable()) {
|
||||
try (var valueTracker = valueTrackerFactory.create()) {
|
||||
listing.cachedValues.clear();
|
||||
VmUtils.readMember(listing, idx);
|
||||
// Must use `readMember` here to force a re-eval of this fact.
|
||||
VmUtils.readMember(listing, idx, callNode);
|
||||
var failure =
|
||||
factFailure(
|
||||
member.getSourceSection(),
|
||||
getDisplayUri(member),
|
||||
valueTracker.values());
|
||||
member.getSourceSection(), getDisplayUri(member), valueTracker.values());
|
||||
resultBuilder.addFailure(failure);
|
||||
}
|
||||
} else {
|
||||
var failure =
|
||||
factFailure(member.getSourceSection(), getDisplayUri(member), null);
|
||||
var failure = factFailure(member.getSourceSection(), getDisplayUri(member), null);
|
||||
resultBuilder.addFailure(failure);
|
||||
}
|
||||
} else {
|
||||
@@ -127,11 +126,9 @@ public final class TestRunner {
|
||||
err.getMessage(), err.toPklException(stackFrameTransformer, useColor));
|
||||
resultBuilder.addError(error);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
testResults.add(resultBuilder.build());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return new TestSectionResults(TestSectionName.FACTS, Collections.unmodifiableList(testResults));
|
||||
}
|
||||
|
||||
@@ -181,23 +178,25 @@ public final class TestRunner {
|
||||
private TestSectionResults doRunAndValidateExamples(
|
||||
VmMapping examples, Path expectedOutputFile, Path actualOutputFile) {
|
||||
var expectedExampleOutputs = loadExampleOutputs(expectedOutputFile);
|
||||
var actualExampleOutputs = new MutableReference<VmDynamic>(null);
|
||||
var allGroupsSucceeded = new MutableBoolean(true);
|
||||
var errored = new MutableBoolean(false);
|
||||
VmDynamic actualExampleOutputs = null;
|
||||
var allGroupsSucceeded = true;
|
||||
var errored = false;
|
||||
var testResults = new ArrayList<TestResult>();
|
||||
examples.forceAndIterateMemberValues(
|
||||
(groupKey, groupMember, groupValue) -> {
|
||||
var callNode = IndirectCallNode.getUncached();
|
||||
|
||||
for (var groupCursor = examples.entries(); groupCursor.advance(); ) {
|
||||
var groupKey = groupCursor.key();
|
||||
var groupMember = groupCursor.member();
|
||||
var testName = String.valueOf(groupKey);
|
||||
var group = (VmListing) groupValue;
|
||||
var expectedGroup =
|
||||
(VmDynamic) VmUtils.readMemberOrNull(expectedExampleOutputs, groupKey);
|
||||
var group = (VmListing) groupCursor.value(callNode);
|
||||
var expectedGroup = (VmDynamic) VmUtils.readMemberOrNull(expectedExampleOutputs, groupKey);
|
||||
var testResultBuilder = new TestResult.Builder(testName);
|
||||
|
||||
if (expectedGroup == null) {
|
||||
testResultBuilder.addFailure(
|
||||
examplePropertyMismatchFailure(getDisplayUri(groupMember), testName, true));
|
||||
testResults.add(testResultBuilder.build());
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (group.getLength() != expectedGroup.getLength()) {
|
||||
@@ -208,44 +207,40 @@ public final class TestRunner {
|
||||
expectedGroup.getLength(),
|
||||
group.getLength()));
|
||||
testResults.add(testResultBuilder.build());
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
group.iterateMembers(
|
||||
((exampleIndex, exampleMember) -> {
|
||||
if (exampleMember.isLocalOrExternalOrHidden()) {
|
||||
return true;
|
||||
}
|
||||
for (var exampleCursor = group.elements(); exampleCursor.advance(); ) {
|
||||
var exampleIndex = exampleCursor.key();
|
||||
|
||||
Object exampleValue;
|
||||
try {
|
||||
exampleValue = VmUtils.readMember(group, exampleIndex);
|
||||
exampleValue = exampleCursor.value(callNode);
|
||||
} catch (VmException err) {
|
||||
errored.set(true);
|
||||
errored = true;
|
||||
testResultBuilder.addError(
|
||||
new TestResults.Error(
|
||||
err.getMessage(), err.toPklException(stackFrameTransformer, useColor)));
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
var expectedValue = VmUtils.readMember(expectedGroup, exampleIndex);
|
||||
var expectedValue = VmUtils.readMember(expectedGroup, exampleIndex, callNode);
|
||||
|
||||
var exampleValuePcf = renderAsPcf(exampleValue);
|
||||
var expectedValuePcf = renderAsPcf(expectedValue);
|
||||
|
||||
if (!(exampleValuePcf.equals(expectedValuePcf))) {
|
||||
if (actualExampleOutputs.isNull()) {
|
||||
if (actualExampleOutputs == null) {
|
||||
// immediately write and load `<fileName>-actual.pcf`
|
||||
// so that we can generate deep link with correct line number for each
|
||||
// mismatch
|
||||
writeExampleOutputs(actualOutputFile, examples);
|
||||
actualExampleOutputs.set(loadExampleOutputs(actualOutputFile));
|
||||
actualExampleOutputs = loadExampleOutputs(actualOutputFile);
|
||||
}
|
||||
|
||||
var expectedMember = VmUtils.findMember(expectedGroup, exampleIndex);
|
||||
assert expectedMember != null;
|
||||
|
||||
var actualGroup =
|
||||
(VmObjectLike) VmUtils.readMemberOrNull(actualExampleOutputs.get(), groupKey);
|
||||
var actualGroup = (VmObjectLike) VmUtils.readMemberOrNull(actualExampleOutputs, groupKey);
|
||||
var actualMember =
|
||||
actualGroup == null ? null : VmUtils.findMember(actualGroup, exampleIndex);
|
||||
if (actualMember == null) {
|
||||
@@ -258,7 +253,7 @@ public final class TestRunner {
|
||||
|
||||
testResultBuilder.addFailure(
|
||||
exampleFailure(
|
||||
getDisplayUri(exampleMember),
|
||||
getDisplayUri(exampleCursor.member()),
|
||||
getDisplayUri(expectedMember),
|
||||
expectedValuePcf,
|
||||
getDisplayUri(actualMember),
|
||||
@@ -267,34 +262,27 @@ public final class TestRunner {
|
||||
} else {
|
||||
testResultBuilder.addSuccess();
|
||||
}
|
||||
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
|
||||
testResults.add(testResultBuilder.build());
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
expectedExampleOutputs.iterateMembers(
|
||||
(groupKey, groupMember) -> {
|
||||
if (groupMember.isLocalOrExternalOrHidden()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (var groupCursor = expectedExampleOutputs.entries(); groupCursor.advance(); ) {
|
||||
var groupKey = groupCursor.key();
|
||||
if (examples.getCachedValue(groupKey) == null) {
|
||||
var testName = String.valueOf(groupKey);
|
||||
allGroupsSucceeded.set(false);
|
||||
allGroupsSucceeded = false;
|
||||
var result =
|
||||
new TestResult.Builder(testName)
|
||||
.addFailure(
|
||||
examplePropertyMismatchFailure(getDisplayUri(groupMember), testName, false))
|
||||
examplePropertyMismatchFailure(
|
||||
getDisplayUri(groupCursor.member()), testName, false))
|
||||
.build();
|
||||
testResults.add(result);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
if (!allGroupsSucceeded.get() && actualExampleOutputs.isNull() && !errored.get()) {
|
||||
if (!allGroupsSucceeded && actualExampleOutputs == null && !errored) {
|
||||
writeExampleOutputs(actualOutputFile, examples);
|
||||
}
|
||||
return new TestSectionResults(
|
||||
@@ -303,40 +291,33 @@ public final class TestRunner {
|
||||
|
||||
private TestSectionResults doRunAndWriteExamples(VmMapping examples, Path outputFile) {
|
||||
var testResults = new ArrayList<TestResult>();
|
||||
var allSucceeded = new MutableBoolean(true);
|
||||
examples.forceAndIterateMemberValues(
|
||||
(groupKey, groupMember, groupValue) -> {
|
||||
var testName = String.valueOf(groupKey);
|
||||
var listing = (VmListing) groupValue;
|
||||
var allSucceeded = true;
|
||||
var callNode = IndirectCallNode.getUncached();
|
||||
for (var cursor = examples.entries(); cursor.advance(); ) {
|
||||
var testName = cursor.keyToString();
|
||||
var groupMember = cursor.member();
|
||||
var listing = (VmListing) cursor.value(callNode);
|
||||
var testResultBuilder = new TestResult.Builder(testName);
|
||||
var success = new MutableBoolean(true);
|
||||
listing.iterateMembers(
|
||||
(idx, member) -> {
|
||||
if (member.isLocalOrExternalOrHidden()) {
|
||||
return true;
|
||||
}
|
||||
var success = true;
|
||||
for (var listingCursor = listing.elements(); listingCursor.advance(); ) {
|
||||
try {
|
||||
VmUtils.readMember(listing, idx);
|
||||
return true;
|
||||
listingCursor.value(callNode);
|
||||
} catch (VmException err) {
|
||||
testResultBuilder.addError(
|
||||
new TestResults.Error(
|
||||
err.getMessage(), err.toPklException(stackFrameTransformer, useColor)));
|
||||
allSucceeded.set(false);
|
||||
success.set(false);
|
||||
return true;
|
||||
allSucceeded = false;
|
||||
success = false;
|
||||
}
|
||||
});
|
||||
if (success.get()) {
|
||||
// treat writing an example as a message
|
||||
}
|
||||
if (success) {
|
||||
testResultBuilder.setExampleWritten(true);
|
||||
testResultBuilder.addFailure(
|
||||
writtenExampleOutputFailure(testName, getDisplayUri(groupMember)));
|
||||
}
|
||||
testResults.add(testResultBuilder.build());
|
||||
return true;
|
||||
});
|
||||
if (allSucceeded.get()) {
|
||||
}
|
||||
if (allSucceeded) {
|
||||
writeExampleOutputs(outputFile, examples);
|
||||
}
|
||||
return new TestSectionResults(
|
||||
|
||||
@@ -864,21 +864,14 @@ public final class VmClass extends VmValue {
|
||||
|
||||
var size = superclass == null ? 0 : superclass.getAllRegularPropertyNames().size();
|
||||
var result = EconomicSet.create(size);
|
||||
if (superclass != null) {
|
||||
result.addAll(superclass.getAllRegularPropertyNames());
|
||||
}
|
||||
for (var property : EconomicMaps.getValues(declaredProperties)) {
|
||||
if (!(property.isLocal() || isHiddenProperty(property.getName()) || property.isExternal())) {
|
||||
result.add(property.getName());
|
||||
}
|
||||
}
|
||||
|
||||
if (superclass == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (result.isEmpty()) {
|
||||
return superclass.getAllRegularPropertyNames();
|
||||
}
|
||||
|
||||
result.addAll(superclass.getAllRegularPropertyNames());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,11 @@ package org.pkl.core.runtime;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import java.util.Iterator;
|
||||
import org.organicdesign.fp.xform.Xform;
|
||||
import org.pkl.core.ast.internal.ReadCursorValueNode;
|
||||
import org.pkl.core.runtime.VmObjectCursor.CursorOption;
|
||||
|
||||
public abstract class VmCollection extends VmValue implements Iterable<Object> {
|
||||
public interface Builder<T extends VmCollection> {
|
||||
@@ -126,18 +129,15 @@ public abstract class VmCollection extends VmValue implements Iterable<Object> {
|
||||
return VmList.create(result);
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
public final VmCollection flatten() {
|
||||
public final VmCollection flatten(VirtualFrame frame, ReadCursorValueNode readCursorValueNode) {
|
||||
var builder = builder();
|
||||
for (var elem : this) {
|
||||
if (elem instanceof Iterable<?> iterable) {
|
||||
builder.addAll(iterable);
|
||||
} else if (elem instanceof VmListing listing) {
|
||||
listing.forceAndIterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
builder.add(value);
|
||||
return true;
|
||||
});
|
||||
for (var cursor = listing.elements(CursorOption.ALL_VALUES); cursor.advance(); ) {
|
||||
builder.add(readCursorValueNode.execute(frame, cursor));
|
||||
}
|
||||
} else {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw new VmExceptionBuilder()
|
||||
|
||||
@@ -17,12 +17,23 @@ package org.pkl.core.runtime;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.frame.MaterializedFrame;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Objects;
|
||||
import org.graalvm.collections.UnmodifiableEconomicMap;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.pkl.core.PClassInfo;
|
||||
import org.pkl.core.PObject;
|
||||
import org.pkl.core.ast.member.ObjectMember;
|
||||
import org.pkl.core.runtime.VmDynamicCursors.CachedElementCursor;
|
||||
import org.pkl.core.runtime.VmDynamicCursors.CachedEntryCursor;
|
||||
import org.pkl.core.runtime.VmDynamicCursors.ElementCursor;
|
||||
import org.pkl.core.runtime.VmDynamicCursors.EntryCursor;
|
||||
import org.pkl.core.runtime.VmDynamicCursors.MemberCursor;
|
||||
import org.pkl.core.runtime.VmDynamicCursors.PropertyCursor;
|
||||
import org.pkl.core.runtime.VmDynamicCursors.UnorderedEntryCursor;
|
||||
import org.pkl.core.runtime.VmDynamicCursors.UnorderedMemberCursor;
|
||||
import org.pkl.core.runtime.VmDynamicCursors.UnorderedPropertyCursor;
|
||||
import org.pkl.core.runtime.VmObjectCursor.CursorOption;
|
||||
import org.pkl.core.util.CollectionUtils;
|
||||
import org.pkl.core.util.EconomicMaps;
|
||||
|
||||
@@ -74,19 +85,16 @@ public final class VmDynamic extends VmObject {
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public PObject export() {
|
||||
assert forced : "Value was not forced prior to export";
|
||||
assert isDeepForced() : "Value was not forced prior to export";
|
||||
|
||||
var properties =
|
||||
CollectionUtils.<String, Object>newLinkedHashMap(EconomicMaps.size(cachedValues));
|
||||
|
||||
iterateAlreadyForcedMemberValues(
|
||||
(key, member, value) -> {
|
||||
properties.put(VmUtils.toPklString(key), VmValue.export(value));
|
||||
return true;
|
||||
});
|
||||
|
||||
// export all members, not just properties
|
||||
for (var cursor = members(); cursor.advance(); ) {
|
||||
CollectionUtils.put(
|
||||
properties, cursor.key().toString(), VmValue.export(cursor.cachedValue()));
|
||||
}
|
||||
return new PObject(PClassInfo.Dynamic, properties);
|
||||
}
|
||||
|
||||
@@ -100,6 +108,77 @@ public final class VmDynamic extends VmObject {
|
||||
return converter.convertDynamic(this, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor properties() {
|
||||
return new PropertyCursor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor properties(CursorOption option) {
|
||||
// never shallow-force because it's impossible to only force properties
|
||||
var anyOrder = option == CursorOption.ANY_ORDER;
|
||||
return anyOrder ? new UnorderedPropertyCursor(this) : new PropertyCursor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor elements() {
|
||||
return new ElementCursor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor elements(CursorOption option) {
|
||||
// never shallow-force because it's impossible to only force elements
|
||||
var anyOrder = option == CursorOption.ANY_ORDER;
|
||||
return anyOrder && isShallowForced() ? new CachedElementCursor(this) : new ElementCursor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor elements(EnumSet<CursorOption> options) {
|
||||
// never shallow-force because it's impossible to only force elements
|
||||
var anyOrder = CollectionUtils.contains(options, CursorOption.ANY_ORDER);
|
||||
return anyOrder && isShallowForced() ? new CachedElementCursor(this) : new ElementCursor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor entries() {
|
||||
return new EntryCursor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor entries(CursorOption option) {
|
||||
// never shallow-force because it's impossible to only force entries
|
||||
var anyOrder = option == CursorOption.ANY_ORDER;
|
||||
if (anyOrder) {
|
||||
return isShallowForced() ? new CachedEntryCursor(this) : new UnorderedEntryCursor(this);
|
||||
}
|
||||
return new EntryCursor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor entries(EnumSet<CursorOption> options) {
|
||||
// never shallow-force because it's impossible to only force entries
|
||||
var anyOrder = CollectionUtils.contains(options, CursorOption.ANY_ORDER);
|
||||
if (anyOrder) {
|
||||
return isShallowForced() ? new CachedEntryCursor(this) : new UnorderedEntryCursor(this);
|
||||
}
|
||||
return new EntryCursor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor members() {
|
||||
return new MemberCursor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor members(CursorOption option) {
|
||||
if (option == CursorOption.ALL_VALUES) {
|
||||
force(false, false);
|
||||
}
|
||||
return option == CursorOption.ANY_ORDER
|
||||
? new UnorderedMemberCursor(this)
|
||||
: new MemberCursor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
@@ -149,10 +228,9 @@ public final class VmDynamic extends VmObject {
|
||||
return result;
|
||||
}
|
||||
|
||||
// assumes object has been forced
|
||||
public int getRegularMemberCount() {
|
||||
assert isShallowForced();
|
||||
if (cachedRegularMemberCount != -1) return cachedRegularMemberCount;
|
||||
|
||||
var result = 0;
|
||||
for (var key : cachedValues.getKeys()) {
|
||||
if (!isHiddenOrLocalProperty(key)) result += 1;
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.core.runtime;
|
||||
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import org.graalvm.collections.EconomicSet;
|
||||
import org.graalvm.collections.UnmodifiableMapCursor;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.pkl.core.ast.member.ObjectMember;
|
||||
import org.pkl.core.runtime.VmObjectCursor.AbstractCachedMemberCursor;
|
||||
import org.pkl.core.runtime.VmObjectCursor.AbstractOrderedCursor;
|
||||
import org.pkl.core.util.ArrayBuilder;
|
||||
import org.pkl.core.util.EconomicSets;
|
||||
import org.pkl.core.util.LateInit;
|
||||
|
||||
final class VmDynamicCursors {
|
||||
private VmDynamicCursors() {}
|
||||
|
||||
static final class PropertyCursor extends AbstractMemberCursor {
|
||||
public PropertyCursor(VmDynamic iteratee) {
|
||||
super(iteratee);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldVisit(ObjectMember member) {
|
||||
return member.isProp()
|
||||
&& member.getNameOrNull() != Identifier.DEFAULT
|
||||
&& !member.isLocalOrExternalOrHidden();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isProperty() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static final class UnorderedPropertyCursor extends AbstractUnorderedMemberCursor {
|
||||
public UnorderedPropertyCursor(VmDynamic iteratee) {
|
||||
super(iteratee);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldVisit(ObjectMember member) {
|
||||
return member.isProp()
|
||||
&& member.getNameOrNull() != Identifier.DEFAULT
|
||||
&& !member.isLocalOrExternalOrHidden();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isProperty() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static final class ElementCursor extends AbstractOrderedCursor<VmDynamic> {
|
||||
private int currentIndex = -1;
|
||||
|
||||
public ElementCursor(VmDynamic iteratee) {
|
||||
super(iteratee);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean advance() {
|
||||
currentIndex += 1;
|
||||
return currentIndex < iteratee.getLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object key() {
|
||||
assert currentIndex >= 0 && currentIndex < iteratee.getLength() : "illegal state";
|
||||
return (long) currentIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return iteratee.getLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isElement() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static final class CachedElementCursor extends AbstractCachedMemberCursor<VmDynamic> {
|
||||
public CachedElementCursor(VmDynamic iteratee) {
|
||||
super(iteratee);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldVisit(Object key) {
|
||||
return key instanceof Long l && l < iteratee.getLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isElement() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static final class EntryCursor extends AbstractMemberCursor {
|
||||
public EntryCursor(VmDynamic iteratee) {
|
||||
super(iteratee);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldVisit(ObjectMember member) {
|
||||
if (member.isProp()) {
|
||||
return false;
|
||||
}
|
||||
return !(key() instanceof Long l) || l >= iteratee.getLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEntry() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static final class UnorderedEntryCursor extends AbstractUnorderedMemberCursor {
|
||||
public UnorderedEntryCursor(VmDynamic iteratee) {
|
||||
super(iteratee);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldVisit(ObjectMember member) {
|
||||
if (member.isProp()) {
|
||||
return false;
|
||||
}
|
||||
return !(key() instanceof Long l) || l >= iteratee.getLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEntry() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static final class CachedEntryCursor extends AbstractCachedMemberCursor<VmDynamic> {
|
||||
public CachedEntryCursor(VmDynamic iteratee) {
|
||||
super(iteratee);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldVisit(Object key) {
|
||||
if (key instanceof Identifier) return false;
|
||||
if (key instanceof Long l) return l >= iteratee.getLength();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEntry() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static final class MemberCursor extends AbstractMemberCursor {
|
||||
public MemberCursor(VmDynamic iteratee) {
|
||||
super(iteratee);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldVisit(ObjectMember member) {
|
||||
return !member.isLocalOrExternalOrHidden() && member.getNameOrNull() != Identifier.DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isProperty() {
|
||||
return key() instanceof Identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isElement() {
|
||||
return key() instanceof Long l && l < iteratee.getLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEntry() {
|
||||
return !isProperty() && !isElement();
|
||||
}
|
||||
}
|
||||
|
||||
static final class UnorderedMemberCursor extends AbstractUnorderedMemberCursor {
|
||||
public UnorderedMemberCursor(VmDynamic iteratee) {
|
||||
super(iteratee);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldVisit(ObjectMember member) {
|
||||
return !member.isLocalOrExternalOrHidden() && member.getNameOrNull() != Identifier.DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isProperty() {
|
||||
return key() instanceof Identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isElement() {
|
||||
return key() instanceof Long l && l < iteratee.getLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEntry() {
|
||||
return !isProperty() && !isElement();
|
||||
}
|
||||
}
|
||||
|
||||
abstract static sealed class AbstractMemberCursor extends AbstractOrderedCursor<VmDynamic> {
|
||||
private final UnmodifiableMapCursor<Object, ObjectMember>[] allMembers;
|
||||
private @LateInit UnmodifiableMapCursor<Object, ObjectMember> currentMembers;
|
||||
private int allMembersIndex;
|
||||
private final EconomicSet<Object> seenKeys = EconomicSets.create();
|
||||
|
||||
public AbstractMemberCursor(VmDynamic iteratee) {
|
||||
super(iteratee);
|
||||
var builder = ArrayBuilder.of(UnmodifiableMapCursor[]::new);
|
||||
for (VmObject object = iteratee;
|
||||
object != null && object.parent != objectPrototype;
|
||||
object = object.parent) {
|
||||
builder.add(object.members.getEntries());
|
||||
}
|
||||
allMembers = builder.toOversizedArray();
|
||||
allMembersIndex = builder.lastIndex();
|
||||
currentMembers = allMembers[allMembersIndex];
|
||||
}
|
||||
|
||||
public abstract boolean shouldVisit(ObjectMember member);
|
||||
|
||||
@Override
|
||||
public boolean advance() {
|
||||
while (true) {
|
||||
while (currentMembers.advance()) {
|
||||
if (EconomicSets.add(seenKeys, currentMembers.getKey())
|
||||
&& shouldVisit(currentMembers.getValue())) return true;
|
||||
}
|
||||
allMembersIndex -= 1;
|
||||
if (allMembersIndex < 0) return false;
|
||||
currentMembers = allMembers[allMembersIndex];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getLength() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object key() {
|
||||
return currentMembers.getKey();
|
||||
}
|
||||
}
|
||||
|
||||
abstract static sealed class AbstractUnorderedMemberCursor extends VmObjectCursor {
|
||||
private static final VmTyped dynamicPrototype = BaseModule.getDynamicClass().getPrototype();
|
||||
|
||||
protected final VmDynamic iteratee;
|
||||
private VmObject currentObject;
|
||||
private UnmodifiableMapCursor<Object, ObjectMember> currentMember;
|
||||
private final EconomicSet<Object> seenKeys = EconomicSets.create();
|
||||
|
||||
public AbstractUnorderedMemberCursor(VmDynamic iteratee) {
|
||||
this.iteratee = iteratee;
|
||||
currentObject = iteratee;
|
||||
currentMember = currentObject.members.getEntries();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VmObject iteratee() {
|
||||
return iteratee;
|
||||
}
|
||||
|
||||
protected abstract boolean shouldVisit(ObjectMember member);
|
||||
|
||||
@Override
|
||||
public final boolean advance() {
|
||||
while (true) {
|
||||
while (currentMember.advance()) {
|
||||
if (EconomicSets.add(seenKeys, currentMember.getKey())
|
||||
&& shouldVisit(currentMember.getValue())) return true;
|
||||
}
|
||||
var parent = currentObject.parent;
|
||||
if (parent == dynamicPrototype) return false;
|
||||
assert parent != null;
|
||||
currentObject = parent;
|
||||
currentMember = currentObject.members.getEntries();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object key() {
|
||||
return currentMember.getKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object value(IndirectCallNode callNode) {
|
||||
var key = key();
|
||||
var value = iteratee.getCachedValue(key);
|
||||
if (value != null) return value;
|
||||
return VmUtils.doReadMember(iteratee, currentObject, key, member(), true, callNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final @Nullable Object cachedValueOrNull() {
|
||||
return iteratee.getCachedValue(key());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getLength() {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,14 @@ package org.pkl.core.runtime;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.RootCallTarget;
|
||||
import com.oracle.truffle.api.frame.MaterializedFrame;
|
||||
import java.util.EnumSet;
|
||||
import java.util.function.BiFunction;
|
||||
import org.graalvm.collections.UnmodifiableEconomicMap;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.pkl.core.ast.PklRootNode;
|
||||
import org.pkl.core.ast.member.ObjectMember;
|
||||
import org.pkl.core.runtime.VmObjectCursor.CursorOption;
|
||||
import org.pkl.core.runtime.VmObjectCursor.EmptyCursor;
|
||||
import org.pkl.core.util.EconomicMaps;
|
||||
|
||||
public final class VmFunction extends VmObjectLike {
|
||||
@@ -139,26 +142,61 @@ public final class VmFunction extends VmObjectLike {
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean iterateMemberValues(MemberValueConsumer consumer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceAndIterateMemberValues(ForcedMemberValueConsumer consumer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean iterateAlreadyForcedMemberValues(ForcedMemberValueConsumer consumer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean iterateMembers(BiFunction<Object, ObjectMember, Boolean> consumer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor properties() {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor properties(CursorOption option) {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor elements() {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor elements(CursorOption option) {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor elements(EnumSet<CursorOption> options) {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor entries() {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor entries(CursorOption option) {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor entries(EnumSet<CursorOption> options) {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor members() {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor members(CursorOption option) {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmClass getVmClass() {
|
||||
return BaseModule.getFunctionNClass(paramCount);
|
||||
|
||||
@@ -18,11 +18,17 @@ package org.pkl.core.runtime;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.frame.MaterializedFrame;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import org.graalvm.collections.UnmodifiableEconomicMap;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.pkl.core.ast.member.ListingOrMappingTypeCastNode;
|
||||
import org.pkl.core.ast.member.ObjectMember;
|
||||
import org.pkl.core.runtime.VmListingCursors.CachedElementCursor;
|
||||
import org.pkl.core.runtime.VmListingCursors.ElementCursor;
|
||||
import org.pkl.core.runtime.VmObjectCursor.CursorOption;
|
||||
import org.pkl.core.runtime.VmObjectCursor.EmptyCursor;
|
||||
import org.pkl.core.util.CollectionUtils;
|
||||
import org.pkl.core.util.EconomicMaps;
|
||||
|
||||
public final class VmListing extends VmListingOrMapping {
|
||||
@@ -62,10 +68,6 @@ public final class VmListing extends VmListingOrMapping {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public static boolean isDefaultProperty(Object propertyKey) {
|
||||
return propertyKey == Identifier.DEFAULT;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
@@ -87,19 +89,13 @@ public final class VmListing extends VmListingOrMapping {
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public List<Object> export() {
|
||||
assert forced : "Value was not forced prior to export";
|
||||
assert isDeepForced() : "Value was not forced prior to export";
|
||||
|
||||
var properties = new ArrayList<>(EconomicMaps.size(cachedValues));
|
||||
|
||||
iterateAlreadyForcedMemberValues(
|
||||
(key, prop, value) -> {
|
||||
if (isDefaultProperty(key)) return true;
|
||||
|
||||
properties.add(VmValue.export(value));
|
||||
return true;
|
||||
});
|
||||
|
||||
return properties;
|
||||
var elements = new ArrayList<>(EconomicMaps.size(cachedValues));
|
||||
for (var cursor = elements(CursorOption.ALL_VALUES); cursor.advance(); ) {
|
||||
elements.add(VmValue.export(cursor.cachedValue()));
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -112,6 +108,77 @@ public final class VmListing extends VmListingOrMapping {
|
||||
return converter.convertListing(this, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public VmObjectCursor elements() {
|
||||
return new ElementCursor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public VmObjectCursor elements(CursorOption option) {
|
||||
if (option == CursorOption.ANY_ORDER) {
|
||||
return isShallowForced() ? new CachedElementCursor(this) : new ElementCursor(this);
|
||||
}
|
||||
if (option == CursorOption.ALL_VALUES) {
|
||||
force(false, false);
|
||||
return new ElementCursor(this);
|
||||
}
|
||||
return new ElementCursor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor elements(EnumSet<CursorOption> options) {
|
||||
var anyOrder = CollectionUtils.contains(options, CursorOption.ANY_ORDER);
|
||||
var allValues = CollectionUtils.contains(options, CursorOption.ALL_VALUES);
|
||||
var lazyRequired = CollectionUtils.contains(options, CursorOption.LAZY_REQUIRED);
|
||||
if (anyOrder) {
|
||||
if (isShallowForced()) {
|
||||
return new CachedElementCursor(this);
|
||||
}
|
||||
if (allValues && !lazyRequired) {
|
||||
force(false, false);
|
||||
return new CachedElementCursor(this);
|
||||
}
|
||||
}
|
||||
return new ElementCursor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor properties() {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor properties(CursorOption option) {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor entries() {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor entries(CursorOption option) {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor entries(EnumSet<CursorOption> options) {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor members() {
|
||||
return elements();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor members(CursorOption option) {
|
||||
return elements(option);
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.core.runtime;
|
||||
|
||||
import org.pkl.core.runtime.VmObjectCursor.AbstractCachedMemberCursor;
|
||||
import org.pkl.core.runtime.VmObjectCursor.AbstractOrderedCursor;
|
||||
|
||||
final class VmListingCursors {
|
||||
private VmListingCursors() {}
|
||||
|
||||
static final class ElementCursor extends AbstractOrderedCursor<VmListing> {
|
||||
private int currentIndex = -1;
|
||||
|
||||
public ElementCursor(VmListing iteratee) {
|
||||
super(iteratee);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean advance() {
|
||||
currentIndex += 1;
|
||||
return currentIndex < iteratee.getLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object key() {
|
||||
assert currentIndex >= 0 && currentIndex < iteratee.getLength() : "illegal state";
|
||||
return (long) currentIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isElement() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return iteratee.getLength();
|
||||
}
|
||||
}
|
||||
|
||||
static final class CachedElementCursor extends AbstractCachedMemberCursor<VmListing> {
|
||||
public CachedElementCursor(VmListing iteratee) {
|
||||
super(iteratee);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldVisit(Object key) {
|
||||
return key instanceof Long; // skip local and hidden properties
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isElement() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ public abstract class VmListingOrMapping extends VmObject {
|
||||
typeCheckOwner = null;
|
||||
}
|
||||
|
||||
public VmListingOrMapping(
|
||||
protected VmListingOrMapping(
|
||||
MaterializedFrame enclosingFrame,
|
||||
@Nullable VmObject parent,
|
||||
UnmodifiableEconomicMap<Object, ObjectMember> members,
|
||||
|
||||
@@ -16,23 +16,27 @@
|
||||
package org.pkl.core.runtime;
|
||||
|
||||
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.frame.MaterializedFrame;
|
||||
import java.util.HashSet;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Map;
|
||||
import org.graalvm.collections.UnmodifiableEconomicMap;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.pkl.core.ast.member.ListingOrMappingTypeCastNode;
|
||||
import org.pkl.core.ast.member.ObjectMember;
|
||||
import org.pkl.core.runtime.VmMappingCursors.CachedEntryCursor;
|
||||
import org.pkl.core.runtime.VmMappingCursors.EntryCursor;
|
||||
import org.pkl.core.runtime.VmObjectCursor.CursorOption;
|
||||
import org.pkl.core.runtime.VmObjectCursor.EmptyCursor;
|
||||
import org.pkl.core.util.CollectionUtils;
|
||||
import org.pkl.core.util.EconomicMaps;
|
||||
import org.pkl.core.util.MutableLong;
|
||||
|
||||
public final class VmMapping extends VmListingOrMapping {
|
||||
private long cachedLength = -1;
|
||||
|
||||
@GuardedBy("this")
|
||||
private @Nullable VmSet __allKeys;
|
||||
private static final EnumSet<CursorOption> isEmptyComputationCursor =
|
||||
EnumSet.of(CursorOption.ANY_ORDER, CursorOption.LAZY_REQUIRED);
|
||||
|
||||
private static final class EmptyHolder {
|
||||
private static final VmMapping EMPTY =
|
||||
@@ -42,6 +46,13 @@ public final class VmMapping extends VmListingOrMapping {
|
||||
EconomicMaps.create());
|
||||
}
|
||||
|
||||
@CompilationFinal private long cachedLength = -1;
|
||||
@CompilationFinal private boolean isEmptyComputed;
|
||||
@CompilationFinal private boolean isEmpty;
|
||||
|
||||
@GuardedBy("this")
|
||||
private @Nullable VmSet __allKeys;
|
||||
|
||||
public static VmMapping empty() {
|
||||
return EmptyHolder.EMPTY;
|
||||
}
|
||||
@@ -63,10 +74,6 @@ public final class VmMapping extends VmListingOrMapping {
|
||||
super(enclosingFrame, parent, members, typeCastNode, typeCheckReceiver, typeCheckOwner);
|
||||
}
|
||||
|
||||
public static boolean isDefaultProperty(Object propertyKey) {
|
||||
return propertyKey == Identifier.DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmClass getVmClass() {
|
||||
return BaseModule.getMappingClass();
|
||||
@@ -91,21 +98,22 @@ public final class VmMapping extends VmListingOrMapping {
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public Map<Object, Object> export() {
|
||||
assert forced : "Value was not forced prior to export";
|
||||
assert isDeepForced() : "Value was not forced prior to export";
|
||||
|
||||
var properties = CollectionUtils.newLinkedHashMap(EconomicMaps.size(cachedValues));
|
||||
var entries = CollectionUtils.newLinkedHashMap(EconomicMaps.size(cachedValues));
|
||||
for (var cursor = entries(); cursor.advance(); ) {
|
||||
entries.put(VmValue.export(cursor.key()), VmValue.export(cursor.cachedValue()));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
iterateAlreadyForcedMemberValues(
|
||||
(key, prop, value) -> {
|
||||
if (isDefaultProperty(key)) return true;
|
||||
|
||||
properties.put(VmValue.export(key), VmValue.export(value));
|
||||
return true;
|
||||
});
|
||||
|
||||
return properties;
|
||||
public Map<Object, Object> toMap(IndirectCallNode callNode) {
|
||||
var entries = CollectionUtils.newLinkedHashMap(EconomicMaps.size(cachedValues));
|
||||
for (var cursor = entries(); cursor.advance(); ) {
|
||||
entries.put(cursor.key(), cursor.value(callNode));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -118,6 +126,73 @@ public final class VmMapping extends VmListingOrMapping {
|
||||
return converter.convertMapping(this, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor properties() {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor properties(CursorOption option) {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor elements() {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor elements(CursorOption option) {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor elements(EnumSet<CursorOption> options) {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
public VmObjectCursor entries() {
|
||||
return new EntryCursor(this);
|
||||
}
|
||||
|
||||
public VmObjectCursor entries(CursorOption option) {
|
||||
if (option == CursorOption.ANY_ORDER) {
|
||||
return isShallowForced() ? new CachedEntryCursor(this) : new EntryCursor(this);
|
||||
}
|
||||
if (option == CursorOption.ALL_VALUES) {
|
||||
force(false, false);
|
||||
return new EntryCursor(this);
|
||||
}
|
||||
return new EntryCursor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor entries(EnumSet<CursorOption> options) {
|
||||
var anyOrder = CollectionUtils.contains(options, CursorOption.ANY_ORDER);
|
||||
var allValues = CollectionUtils.contains(options, CursorOption.ALL_VALUES);
|
||||
var lazyRequired = CollectionUtils.contains(options, CursorOption.LAZY_REQUIRED);
|
||||
if (anyOrder) {
|
||||
if (isShallowForced()) {
|
||||
return new CachedEntryCursor(this);
|
||||
}
|
||||
if (allValues && !lazyRequired) {
|
||||
force(false, false);
|
||||
return new CachedEntryCursor(this);
|
||||
}
|
||||
}
|
||||
return new EntryCursor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor members() {
|
||||
return entries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor members(CursorOption option) {
|
||||
return entries(option);
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
@@ -167,25 +242,23 @@ public final class VmMapping extends VmListingOrMapping {
|
||||
return result;
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
public long getLength() {
|
||||
if (cachedLength != -1) return cachedLength;
|
||||
var count = new MutableLong(0);
|
||||
var visited = new HashSet<>();
|
||||
iterateMembers(
|
||||
(key, member) -> {
|
||||
var alreadyVisited = !visited.add(key);
|
||||
// important to record hidden member as visited before skipping it
|
||||
// because any overriding member won't carry a `hidden` identifier
|
||||
if (alreadyVisited || member.isLocalOrExternalOrHidden()) return true;
|
||||
count.getAndIncrement();
|
||||
return true;
|
||||
});
|
||||
cachedLength = count.get();
|
||||
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||
var count = 0;
|
||||
for (var cursor = entries(CursorOption.LAZY_REQUIRED); cursor.advance(); ) {
|
||||
count++;
|
||||
}
|
||||
cachedLength = count;
|
||||
return cachedLength;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return getLength() == 0;
|
||||
if (!isEmptyComputed) {
|
||||
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||
isEmptyComputed = true;
|
||||
isEmpty = !entries(isEmptyComputationCursor).advance();
|
||||
}
|
||||
return isEmpty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.core.runtime;
|
||||
|
||||
import java.util.Iterator;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.pkl.core.runtime.VmObjectCursor.AbstractCachedMemberCursor;
|
||||
import org.pkl.core.runtime.VmObjectCursor.AbstractOrderedCursor;
|
||||
|
||||
final class VmMappingCursors {
|
||||
private VmMappingCursors() {}
|
||||
|
||||
static final class EntryCursor extends AbstractOrderedCursor<VmMapping> {
|
||||
private final Iterator<Object> keys;
|
||||
private @Nullable Object currentKey;
|
||||
|
||||
public EntryCursor(VmMapping iteratee) {
|
||||
super(iteratee);
|
||||
keys = iteratee.getAllKeys().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean advance() {
|
||||
if (keys.hasNext()) {
|
||||
currentKey = keys.next();
|
||||
return true;
|
||||
}
|
||||
currentKey = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object key() {
|
||||
assert currentKey != null : "illegal state";
|
||||
return currentKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEntry() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static final class CachedEntryCursor extends AbstractCachedMemberCursor<VmMapping> {
|
||||
public CachedEntryCursor(VmMapping iteratee) {
|
||||
super(iteratee);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldVisit(Object key) {
|
||||
return !(key instanceof Identifier); // skip local and hidden properties
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEntry() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.pkl.core.runtime;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.frame.MaterializedFrame;
|
||||
@@ -29,12 +30,18 @@ import org.pkl.core.util.EconomicMaps;
|
||||
|
||||
/** Corresponds to `pkl.base#Object`. */
|
||||
public abstract class VmObject extends VmObjectLike {
|
||||
private static final byte START_SHALLOW_FORCE_FLAG = 0x1;
|
||||
private static final byte START_DEEP_FORCE_FLAG = 0x2;
|
||||
private static final byte SHALLOW_FORCE_FLAG = 0x4;
|
||||
private static final byte DEEP_FORCE_FLAG = 0x8;
|
||||
|
||||
@CompilationFinal protected @Nullable VmObject parent;
|
||||
protected final UnmodifiableEconomicMap<Object, ObjectMember> members;
|
||||
protected final EconomicMap<Object, Object> cachedValues;
|
||||
|
||||
protected int cachedHash;
|
||||
protected boolean forced;
|
||||
private byte flags;
|
||||
private final int depth;
|
||||
|
||||
public VmObject(
|
||||
MaterializedFrame enclosingFrame,
|
||||
@@ -42,6 +49,7 @@ public abstract class VmObject extends VmObjectLike {
|
||||
UnmodifiableEconomicMap<Object, ObjectMember> members,
|
||||
EconomicMap<Object, Object> cachedValues) {
|
||||
super(enclosingFrame);
|
||||
depth = parent == null ? 0 : parent.depth + 1;
|
||||
this.parent = parent;
|
||||
this.members = members;
|
||||
this.cachedValues = cachedValues;
|
||||
@@ -71,6 +79,16 @@ public abstract class VmObject extends VmObjectLike {
|
||||
return EconomicMaps.containsKey(members, key);
|
||||
}
|
||||
|
||||
public final @Nullable ObjectMember getRootFirstMember(Object key) {
|
||||
if (parent != null) {
|
||||
var parentMember = parent.getRootFirstMember(key);
|
||||
if (parentMember != null) {
|
||||
return parentMember;
|
||||
}
|
||||
}
|
||||
return getMember(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final @Nullable ObjectMember getMember(Object key) {
|
||||
return EconomicMaps.get(members, key);
|
||||
@@ -96,43 +114,6 @@ public abstract class VmObject extends VmObjectLike {
|
||||
return EconomicMaps.containsKey(cachedValues, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public final boolean iterateMemberValues(MemberValueConsumer consumer) {
|
||||
var visited = new HashSet<>();
|
||||
return iterateMembers(
|
||||
(key, member) -> {
|
||||
var alreadyVisited = !visited.add(key);
|
||||
// important to record hidden member as visited before skipping it
|
||||
// because any overriding member won't carry a `hidden` identifier
|
||||
if (alreadyVisited || member.isLocalOrExternalOrHidden()) return true;
|
||||
return consumer.accept(key, member, getCachedValue(key));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public final boolean forceAndIterateMemberValues(ForcedMemberValueConsumer consumer) {
|
||||
force(false, false);
|
||||
return iterateAlreadyForcedMemberValues(consumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public final boolean iterateAlreadyForcedMemberValues(ForcedMemberValueConsumer consumer) {
|
||||
var visited = new HashSet<>();
|
||||
return iterateMembers(
|
||||
(key, member) -> {
|
||||
var alreadyVisited = !visited.add(key);
|
||||
// important to record hidden member as visited before skipping it
|
||||
// because any overriding member won't carry a `hidden` identifier
|
||||
if (alreadyVisited || member.isLocalOrExternalOrHidden()) return true;
|
||||
Object cachedValue = getCachedValue(key);
|
||||
assert cachedValue != null; // forced
|
||||
return consumer.accept(key, member, cachedValue);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public final boolean iterateMembers(BiFunction<Object, ObjectMember, Boolean> consumer) {
|
||||
@@ -150,14 +131,33 @@ public abstract class VmObject extends VmObjectLike {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean isShallowForced() {
|
||||
return (flags & (SHALLOW_FORCE_FLAG | DEEP_FORCE_FLAG)) != 0;
|
||||
}
|
||||
|
||||
protected boolean isDeepForced() {
|
||||
return (flags & DEEP_FORCE_FLAG) != 0;
|
||||
}
|
||||
|
||||
protected boolean isStartDeepForce() {
|
||||
return (flags & START_DEEP_FORCE_FLAG) != 0;
|
||||
}
|
||||
|
||||
protected boolean isStartShallowForce() {
|
||||
return (flags & (START_SHALLOW_FORCE_FLAG | START_DEEP_FORCE_FLAG)) != 0;
|
||||
}
|
||||
|
||||
/** Evaluates this object's members. Skips local, hidden, and external members. */
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public final void force(boolean allowUndefinedValues, boolean recurse) {
|
||||
if (forced) return;
|
||||
|
||||
// set eagerly to break reference cycles
|
||||
if (recurse) forced = true;
|
||||
var oldFlags = flags;
|
||||
if (recurse) {
|
||||
if (isStartDeepForce()) return;
|
||||
flags |= (START_DEEP_FORCE_FLAG | START_SHALLOW_FORCE_FLAG);
|
||||
} else {
|
||||
if (isStartShallowForce()) return;
|
||||
flags |= START_SHALLOW_FORCE_FLAG;
|
||||
}
|
||||
|
||||
var fullyForced = true;
|
||||
|
||||
@@ -191,12 +191,17 @@ public abstract class VmObject extends VmObjectLike {
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
forced = false;
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
flags = oldFlags;
|
||||
throw t;
|
||||
}
|
||||
|
||||
// make sure uncached values are not marked as forced
|
||||
if (recurse && !fullyForced) forced = false;
|
||||
if (recurse && !fullyForced) {
|
||||
flags = 0;
|
||||
} else {
|
||||
flags |= recurse ? DEEP_FORCE_FLAG : SHALLOW_FORCE_FLAG;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -218,17 +223,14 @@ public abstract class VmObject extends VmObjectLike {
|
||||
*/
|
||||
@TruffleBoundary
|
||||
protected final Map<String, Object> exportMembers() {
|
||||
Map<String, Object> result = CollectionUtils.newLinkedHashMap(EconomicMaps.size(cachedValues));
|
||||
assert forced : "Value was not forced prior to export";
|
||||
|
||||
iterateAlreadyForcedMemberValues(
|
||||
(key, member, value) -> {
|
||||
if (member.isClass() || member.isTypeAlias()) return true;
|
||||
|
||||
result.put(key.toString(), VmValue.export(value));
|
||||
return true;
|
||||
});
|
||||
assert isDeepForced() : "Value was not forced prior to export";
|
||||
|
||||
var result = CollectionUtils.<String, Object>newLinkedHashMap(EconomicMaps.size(cachedValues));
|
||||
for (var cursor = members(); cursor.advance(); ) {
|
||||
var member = cursor.member();
|
||||
if (member.isType()) continue;
|
||||
CollectionUtils.put(result, cursor.key().toString(), VmValue.export(cursor.cachedValue()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.core.runtime;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import org.graalvm.collections.UnmodifiableMapCursor;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.pkl.core.ast.member.ObjectMember;
|
||||
|
||||
/**
|
||||
* A cursor for iterating over the keys and values of {@link VmObject} members.
|
||||
*
|
||||
* <p>To obtain a cursor, call one of the following methods:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link VmObjectLike#properties}
|
||||
* <li>{@link VmObjectLike#elements}
|
||||
* <li>{@link VmObjectLike#entries}
|
||||
* <li>{@link VmObjectLike#members}
|
||||
* </ul>
|
||||
*
|
||||
* To customize cursor behavior, the above methods optionally accept a {@link CursorOption}.
|
||||
*
|
||||
* <p>A typical cursor iteration looks as follows:
|
||||
*
|
||||
* <pre><code>
|
||||
* for (var cursor = object.properties(); cursor.advance(); ) {
|
||||
* var key = cursor.key();
|
||||
* var value = cursor.value();
|
||||
* ...
|
||||
* }
|
||||
* </code></pre>
|
||||
*
|
||||
* <p>Cursors do not visit {@code local}, {@code external}, and {@code hidden} properties. Only
|
||||
* {@link VmTyped#members} visits {@linkplain ObjectMember#isClass() classes} and {@linkplain
|
||||
* ObjectMember#isTypeAlias() type aliases} of {@linkplain VmObjectLike#isModuleObject() module
|
||||
* objects}.
|
||||
*/
|
||||
public abstract class VmObjectCursor {
|
||||
protected static final VmTyped objectPrototype = BaseModule.getObjectClass().getPrototype();
|
||||
|
||||
public enum CursorOption {
|
||||
/**
|
||||
* Indicates that a cursor may choose any iteration order.
|
||||
*
|
||||
* <p>Specifying this option may increase cursor performance.
|
||||
*
|
||||
* <p>If this option is not specified, members are visited in declaration order. For elements,
|
||||
* declaration order is equivalent to ascending order.
|
||||
*/
|
||||
ANY_ORDER,
|
||||
/**
|
||||
* Indicates that the caller will request every value of this cursor unless it encounters an
|
||||
* unexpected error.
|
||||
*
|
||||
* <p>Specifying this option may increase cursor performance.
|
||||
*/
|
||||
ALL_VALUES,
|
||||
/**
|
||||
* Indicates that a cursor must not evaluate any member before its value is requested. This
|
||||
* option should only be specified if a lazyness guarantee is required for correctness.
|
||||
*/
|
||||
LAZY_REQUIRED
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances this cursor to the next object member.
|
||||
*
|
||||
* @return {@code true} if a next member exists, {@code false} otherwise
|
||||
*/
|
||||
public abstract boolean advance();
|
||||
|
||||
/**
|
||||
* Returns the key of the current object member.
|
||||
*
|
||||
* <p>The returned key has one of the following types:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link Identifier} for a property
|
||||
* <li>{@link Long} for an element
|
||||
* <li>{@link Boolean}, {@link Long}, {@link Double}, {@link String} or {@link VmValue} for an
|
||||
* entry
|
||||
* </ul>
|
||||
*
|
||||
* @throws RuntimeException if this method is called before the first call to {@link #advance()}
|
||||
* or after {@link #advance()} returned {@code false}
|
||||
*/
|
||||
public abstract Object key();
|
||||
|
||||
/**
|
||||
* Returns the value of the current object member.
|
||||
*
|
||||
* <p>If the member's value is not already cached, the member is evaluated.
|
||||
*
|
||||
* <p>If calling from a node, use {@link org.pkl.core.ast.internal.ReadCursorValueNode} instead of
|
||||
* calling this directly.
|
||||
*
|
||||
* @throws RuntimeException if this method is called before the first call to {@link #advance()}
|
||||
* or after {@link #advance()} returned {@code false}
|
||||
*/
|
||||
public abstract Object value(IndirectCallNode callNode);
|
||||
|
||||
/** Returns the cached value for this member, or {@code null} if it does not exist. */
|
||||
public abstract @Nullable Object cachedValueOrNull();
|
||||
|
||||
/** Returns the cached value for this member, asserting that it exists. */
|
||||
public final Object cachedValue() {
|
||||
var value = cachedValueOrNull();
|
||||
assert value != null;
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean isProperty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isElement() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isEntry() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract VmObject iteratee();
|
||||
|
||||
/**
|
||||
* Returns the current member.
|
||||
*
|
||||
* <p>If a member is overridden in the prototype chain, this method returns the original
|
||||
* definition, whereas {@link #value(IndirectCallNode)} returns the value corresponding to the
|
||||
* overriding definition. As a consequence, {@code member().isElement()} will correctly identify
|
||||
* elements overridden with entry syntax.
|
||||
*
|
||||
* <p>This method is supported by the following cursors:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link VmObjectLike#members}
|
||||
* </ul>
|
||||
*/
|
||||
public final ObjectMember member() {
|
||||
var key = key();
|
||||
var iteratee = iteratee();
|
||||
var ret = iteratee.getRootFirstMember(key);
|
||||
assert ret != null;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
public final String keyToString() {
|
||||
return key().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* The length of the members to be iterated.
|
||||
*
|
||||
* <p>Returns {@code -1} if the length is not statically known (requires evaluation to compute).
|
||||
*/
|
||||
public abstract int getLength();
|
||||
|
||||
static final class EmptyCursor extends VmObjectCursor {
|
||||
public static final EmptyCursor INSTANCE = new EmptyCursor();
|
||||
|
||||
private EmptyCursor() {}
|
||||
|
||||
@Override
|
||||
public boolean advance() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object key() {
|
||||
throw new IllegalStateException("empty cursor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object value(IndirectCallNode ignored) {
|
||||
throw new IllegalStateException("empty cursor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object cachedValueOrNull() {
|
||||
throw new IllegalStateException("empty cursor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isProperty() {
|
||||
throw new IllegalStateException("empty cursor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isElement() {
|
||||
throw new IllegalStateException("empty cursor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEntry() {
|
||||
throw new IllegalStateException("empty cursor");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VmObject iteratee() {
|
||||
throw new IllegalStateException("empty cursor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
abstract static class AbstractOrderedCursor<T extends VmObject> extends VmObjectCursor {
|
||||
protected final T iteratee;
|
||||
|
||||
public AbstractOrderedCursor(T iteratee) {
|
||||
this.iteratee = iteratee;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object value(IndirectCallNode callNode) {
|
||||
var key = key();
|
||||
var value = iteratee.getCachedValue(key);
|
||||
if (value != null) return value;
|
||||
// find and evaluate bottom-most member for key (simplified version of VmUtils.readMember())
|
||||
for (VmObject object = iteratee;
|
||||
object != null && object.parent != objectPrototype;
|
||||
object = object.parent) {
|
||||
var member = object.getMember(key);
|
||||
if (member != null) {
|
||||
return VmUtils.doReadMember(iteratee, object, key, member, true, callNode);
|
||||
}
|
||||
}
|
||||
throw unreachableCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final @Nullable Object cachedValueOrNull() {
|
||||
return iteratee.getCachedValue(key());
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
private VmException unreachableCode() {
|
||||
return new VmExceptionBuilder().unreachableCode().build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VmObject iteratee() {
|
||||
return iteratee;
|
||||
}
|
||||
}
|
||||
|
||||
abstract static class AbstractCachedMemberCursor<T extends VmObject> extends VmObjectCursor {
|
||||
protected final T iteratee;
|
||||
private final UnmodifiableMapCursor<Object, Object> cachedValues;
|
||||
@CompilationFinal private int length = -1;
|
||||
|
||||
@TruffleBoundary
|
||||
AbstractCachedMemberCursor(T iteratee) {
|
||||
this.iteratee = iteratee;
|
||||
cachedValues = iteratee.cachedValues.getEntries();
|
||||
}
|
||||
|
||||
protected abstract boolean shouldVisit(Object key);
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public boolean advance() {
|
||||
while (true) {
|
||||
if (!cachedValues.advance()) return false;
|
||||
if (shouldVisit(cachedValues.getKey())) return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public Object key() {
|
||||
return cachedValues.getKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public final Object value(IndirectCallNode ignored) {
|
||||
return cachedValues.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object cachedValueOrNull() {
|
||||
return cachedValues.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VmObject iteratee() {
|
||||
return iteratee;
|
||||
}
|
||||
|
||||
public final int getLength() {
|
||||
if (length == -1) {
|
||||
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||
length = computeLength();
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
private int computeLength() {
|
||||
var ret = 0;
|
||||
var cursor = iteratee.cachedValues.getEntries();
|
||||
while (cursor.advance()) {
|
||||
if (shouldVisit(cursor.getKey())) {
|
||||
ret++;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,12 @@ package org.pkl.core.runtime;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.frame.MaterializedFrame;
|
||||
import java.util.EnumSet;
|
||||
import java.util.function.BiFunction;
|
||||
import org.graalvm.collections.UnmodifiableEconomicMap;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.pkl.core.ast.member.ObjectMember;
|
||||
import org.pkl.core.runtime.VmObjectCursor.CursorOption;
|
||||
|
||||
/**
|
||||
* Corresponds to `pkl.base#Object|pkl.base#Function`. The lexical scope is a chain of
|
||||
@@ -140,35 +142,34 @@ public abstract class VmObjectLike extends VmValue {
|
||||
@TruffleBoundary
|
||||
public abstract boolean hasCachedValue(Object key);
|
||||
|
||||
/**
|
||||
* Iterates over member definitions and their values in order of their definition, from the top of
|
||||
* the prototype chain downwards. If a member value has not yet been evaluated, a `null` `value`
|
||||
* is passed to `consumer`. If a member is defined in multiple objects in the prototype chain,
|
||||
* i.e., is overridden along the way, it is visited only once, with the initial (i.e., upmost)
|
||||
* `member` and the final (i.e., downmost) `value`. (This peculiar behavior serves two purposes in
|
||||
* the current implementation: it guarantees that a `hidden` property is still recognized as such
|
||||
* when overridden, and that an element is still recognized as such when overridden with entry
|
||||
* syntax. It also means that members are visited in order of (first) definition.) Local, hidden,
|
||||
* and external properties are not visited. If an invocation of `consumer` returns `false`, the
|
||||
* remaining members are not visited, and `false` is returned. Otherwise, all members are visited,
|
||||
* and `true` is returned.
|
||||
*/
|
||||
public abstract boolean iterateMemberValues(MemberValueConsumer consumer);
|
||||
public abstract VmObjectCursor properties();
|
||||
|
||||
/**
|
||||
* Same as {@link #iterateMemberValues} except that it first performs a shallow {@link #force}. As
|
||||
* a consequence, values passed to {@code consumer} are guaranteed to be non-null.
|
||||
*/
|
||||
public abstract boolean forceAndIterateMemberValues(ForcedMemberValueConsumer consumer);
|
||||
public abstract VmObjectCursor properties(CursorOption option);
|
||||
|
||||
public abstract boolean iterateAlreadyForcedMemberValues(ForcedMemberValueConsumer consumer);
|
||||
public abstract VmObjectCursor elements();
|
||||
|
||||
public abstract VmObjectCursor elements(CursorOption option);
|
||||
|
||||
public abstract VmObjectCursor elements(EnumSet<CursorOption> options);
|
||||
|
||||
public abstract VmObjectCursor entries();
|
||||
|
||||
public abstract VmObjectCursor entries(CursorOption option);
|
||||
|
||||
public abstract VmObjectCursor entries(EnumSet<CursorOption> options);
|
||||
|
||||
public abstract VmObjectCursor members();
|
||||
|
||||
public abstract VmObjectCursor members(CursorOption option);
|
||||
|
||||
/**
|
||||
* Iterates over member definitions in order of their definition, from the top of the prototype
|
||||
* chain downwards. If a member is defined multiple times, each occurrence is visited. Local
|
||||
* properties are not visited. If an invocation of `consumer` returns `false`, the remaining
|
||||
* members are not visited, and `false` is returned. Otherwise, all members are visited, and
|
||||
* `true` is returned.
|
||||
* properties are not visited. If an invocation of {@code consumer} returns {@code false}, the
|
||||
* remaining members are not visited, and {@code false} is returned. Otherwise, all members are
|
||||
* visited, and {@code true} is returned.
|
||||
*
|
||||
* <p>Unlike {@link #members()}, this will also visit hidden properties.
|
||||
*/
|
||||
public abstract boolean iterateMembers(BiFunction<Object, ObjectMember, Boolean> consumer);
|
||||
|
||||
@@ -180,22 +181,4 @@ public abstract class VmObjectLike extends VmValue {
|
||||
* properties
|
||||
*/
|
||||
public abstract Object export();
|
||||
|
||||
@FunctionalInterface
|
||||
public interface MemberValueConsumer {
|
||||
/**
|
||||
* Returns true if {@link #iterateMemberValues} should continue calling this method for the
|
||||
* remaining members, and false otherwise.
|
||||
*/
|
||||
boolean accept(Object key, ObjectMember member, @Nullable Object value);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ForcedMemberValueConsumer {
|
||||
/**
|
||||
* Returns true if {@link #forceAndIterateMemberValues} should continue calling this method for
|
||||
* the remaining members, and false otherwise.
|
||||
*/
|
||||
boolean accept(Object key, ObjectMember member, Object value);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.frame.MaterializedFrame;
|
||||
import com.oracle.truffle.api.instrumentation.InstrumentableNode.WrapperNode;
|
||||
import java.util.EnumSet;
|
||||
import org.graalvm.collections.EconomicMap;
|
||||
import org.graalvm.collections.UnmodifiableEconomicMap;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
@@ -28,6 +29,9 @@ import org.pkl.core.PObject;
|
||||
import org.pkl.core.ast.ExpressionNode;
|
||||
import org.pkl.core.ast.expression.unary.ImportNode;
|
||||
import org.pkl.core.ast.member.ObjectMember;
|
||||
import org.pkl.core.runtime.VmObjectCursor.CursorOption;
|
||||
import org.pkl.core.runtime.VmObjectCursor.EmptyCursor;
|
||||
import org.pkl.core.runtime.VmTypedCursors.PropertyCursor;
|
||||
import org.pkl.core.util.EconomicMaps;
|
||||
|
||||
public final class VmTyped extends VmObject {
|
||||
@@ -165,7 +169,7 @@ public final class VmTyped extends VmObject {
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public Composite export() {
|
||||
assert forced : "Value was not forced prior to export";
|
||||
assert isDeepForced() : "Value was not forced prior to export";
|
||||
assert clazz != null;
|
||||
if (!isModuleObject()) {
|
||||
return new PObject(clazz.getPClassInfo(), exportMembers());
|
||||
@@ -189,6 +193,63 @@ public final class VmTyped extends VmObject {
|
||||
return converter.convertTyped(this, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor properties() {
|
||||
return new PropertyCursor(this, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor properties(CursorOption option) {
|
||||
// don't force module objects to avoid forcing types (too conservative?)
|
||||
if (option == CursorOption.ALL_VALUES && !isModuleObject()) {
|
||||
force(false, false);
|
||||
}
|
||||
return new PropertyCursor(this, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor elements() {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor elements(CursorOption option) {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor elements(EnumSet<CursorOption> options) {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor entries() {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor entries(CursorOption option) {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor entries(EnumSet<CursorOption> options) {
|
||||
return EmptyCursor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor members() {
|
||||
return new PropertyCursor(this, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmObjectCursor members(CursorOption option) {
|
||||
if (option == CursorOption.ANY_ORDER) {
|
||||
force(false, false);
|
||||
}
|
||||
return new PropertyCursor(this, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.core.runtime;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import java.util.Iterator;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.pkl.core.runtime.VmObjectCursor.AbstractOrderedCursor;
|
||||
|
||||
// Doesn't offer CachedPropertyCursor because filtering out local, hidden, and type properties
|
||||
// while iterating over VmObject.cachedValues likely won't be any better than using PropertyCursor
|
||||
// in the first place.
|
||||
final class VmTypedCursors {
|
||||
private VmTypedCursors() {}
|
||||
|
||||
static final class PropertyCursor extends AbstractOrderedCursor<VmTyped> {
|
||||
private final Iterator<Identifier> identifiers;
|
||||
private final boolean skipTypes;
|
||||
private @Nullable Object currentKey;
|
||||
@CompilationFinal int length = -1;
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public PropertyCursor(VmTyped iteratee, boolean skipTypes) {
|
||||
super(iteratee);
|
||||
identifiers = (Iterator) iteratee.getVmClass().getAllRegularPropertyNames().iterator();
|
||||
this.skipTypes = skipTypes && iteratee.isModuleObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
@TruffleBoundary
|
||||
public boolean advance() {
|
||||
if (identifiers.hasNext()) {
|
||||
currentKey = identifiers.next();
|
||||
if (skipTypes) {
|
||||
while (member().isType()) {
|
||||
if (identifiers.hasNext()) {
|
||||
currentKey = identifiers.next();
|
||||
} else {
|
||||
currentKey = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
currentKey = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object key() {
|
||||
assert currentKey != null : "illegal state";
|
||||
return currentKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isProperty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
if (length == -1) {
|
||||
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||
length = computeLength();
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
private int computeLength() {
|
||||
var length = 0;
|
||||
var clazz = iteratee.getVmClass();
|
||||
for (var identifier : iteratee.getVmClass().getAllRegularPropertyNames()) {
|
||||
var member = clazz.getProperty((Identifier) identifier);
|
||||
assert member != null;
|
||||
if (skipTypes && member.isType()) {
|
||||
continue;
|
||||
}
|
||||
length++;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,7 @@ import org.pkl.core.ast.type.UnresolvedTypeNode;
|
||||
import org.pkl.core.module.ModuleKey;
|
||||
import org.pkl.core.module.ModuleKeys;
|
||||
import org.pkl.core.module.ResolvedModuleKey;
|
||||
import org.pkl.core.runtime.VmObjectCursor.CursorOption;
|
||||
import org.pkl.core.util.EconomicMaps;
|
||||
import org.pkl.core.util.Pair;
|
||||
import org.pkl.parser.Parser;
|
||||
@@ -293,43 +294,43 @@ public final class VmUtils {
|
||||
}
|
||||
|
||||
public static Map<String, FileOutput> readFilesProperty(
|
||||
VmObjectLike receiver, Function<VmTyped, FileOutput> fileOutputFactory) {
|
||||
var filesOrNull = VmUtils.readMember(receiver, Identifier.FILES);
|
||||
VmObjectLike receiver,
|
||||
Function<VmTyped, FileOutput> fileOutputFactory,
|
||||
IndirectCallNode callNode) {
|
||||
var filesOrNull = VmUtils.readMember(receiver, Identifier.FILES, callNode);
|
||||
if (filesOrNull instanceof VmNull) {
|
||||
return Map.of();
|
||||
}
|
||||
var files = (VmMapping) filesOrNull;
|
||||
var result = new LinkedHashMap<String, FileOutput>();
|
||||
files.forceAndIterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
for (var cursor = files.members(CursorOption.ALL_VALUES); cursor.advance(); ) {
|
||||
var key = cursor.key();
|
||||
var member = cursor.member();
|
||||
assert member.isEntry();
|
||||
result.put((String) key, fileOutputFactory.apply((VmTyped) value));
|
||||
return true;
|
||||
});
|
||||
var value = (VmTyped) cursor.value(callNode);
|
||||
result.put((String) key, fileOutputFactory.apply(value));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
public static Object readMember(VmObjectLike receiver, Object memberKey) {
|
||||
var result = readMemberOrNull(receiver, memberKey);
|
||||
if (result != null) return result;
|
||||
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw new VmExceptionBuilder().cannotFindMember(receiver, memberKey).build();
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
public static @Nullable Object readMemberOrNull(
|
||||
VmObjectLike receiver, Object memberKey, boolean checkType) {
|
||||
return readMemberOrNull(receiver, memberKey, checkType, IndirectCallNode.getUncached());
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
public static @Nullable Object readMemberOrNull(
|
||||
VmObjectLike receiver, Object memberKey, IndirectCallNode callNode) {
|
||||
return readMemberOrNull(receiver, memberKey, true, callNode);
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
public static @Nullable Object readMemberOrNull(VmObjectLike receiver, Object memberKey) {
|
||||
var cachedValue = receiver.getCachedValue(memberKey);
|
||||
if (cachedValue != null) {
|
||||
@@ -342,17 +343,16 @@ public final class VmUtils {
|
||||
* Before calling this method, always try `VmObject.getCachedValue()`. (This method writes to the
|
||||
* cache, but doesn't read from it.)
|
||||
*/
|
||||
@TruffleBoundary
|
||||
public static Object doReadMember(
|
||||
VmObjectLike receiver, VmObjectLike owner, Object memberKey, ObjectMember member) {
|
||||
return doReadMember(receiver, owner, memberKey, member, true, IndirectCallNode.getUncached());
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
public static Object readMember(
|
||||
VmObjectLike receiver, Object memberKey, IndirectCallNode callNode) {
|
||||
var result = readMemberOrNull(receiver, memberKey, true, callNode);
|
||||
if (result != null) return result;
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
|
||||
throw new VmExceptionBuilder()
|
||||
.cannotFindMember(receiver, memberKey)
|
||||
@@ -360,13 +360,12 @@ public final class VmUtils {
|
||||
.build();
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
public static @Nullable Object readMemberOrNull(
|
||||
VmObjectLike receiver, Object memberKey, boolean checkType, IndirectCallNode callNode) {
|
||||
assert (!(memberKey instanceof Identifier identifier) || !identifier.isLocalProp())
|
||||
: "Must use ReadLocalPropertyNode for local properties.";
|
||||
|
||||
final var cachedValue = receiver.getCachedValue(memberKey);
|
||||
var cachedValue = receiver.getCachedValue(memberKey);
|
||||
if (cachedValue != null) return cachedValue;
|
||||
|
||||
for (var owner = receiver; owner != null; owner = owner.getParent()) {
|
||||
@@ -382,7 +381,6 @@ public final class VmUtils {
|
||||
* Before calling this method, always try `VmObject.getCachedValue()`. (This method writes to the
|
||||
* cache, but doesn't read from it.)
|
||||
*/
|
||||
@TruffleBoundary
|
||||
public static Object doReadMember(
|
||||
VmObjectLike receiver,
|
||||
VmObjectLike owner,
|
||||
@@ -604,6 +602,11 @@ public final class VmUtils {
|
||||
builder.append(string);
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
public static void appendToBuilder(StringBuilder builder, Object object) {
|
||||
builder.append(object);
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
public static String builderToString(StringBuilder builder) {
|
||||
return builder.toString();
|
||||
|
||||
@@ -18,7 +18,7 @@ package org.pkl.core.runtime;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import org.pkl.core.ValueFormatter;
|
||||
import org.pkl.core.util.MutableBoolean;
|
||||
import org.pkl.core.runtime.VmObjectCursor.CursorOption;
|
||||
import org.pkl.parser.Lexer;
|
||||
|
||||
/**
|
||||
@@ -332,15 +332,17 @@ public final class VmValueRenderer {
|
||||
|
||||
currIndent += indent;
|
||||
|
||||
var isEmpty = new MutableBoolean(true);
|
||||
object.iterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
var isEmpty = true;
|
||||
for (var cursor = object.members(CursorOption.LAZY_REQUIRED); cursor.advance(); ) {
|
||||
var key = cursor.key();
|
||||
var value = cursor.cachedValueOrNull();
|
||||
var member = cursor.member();
|
||||
// don't render type definitions
|
||||
if (member.isClass() || member.isTypeAlias()) return true;
|
||||
if (member.isType()) continue;
|
||||
|
||||
if (isEmpty.get()) {
|
||||
if (isEmpty) {
|
||||
append(leadingOrTrailingNewline);
|
||||
isEmpty.set(false);
|
||||
isEmpty = false;
|
||||
}
|
||||
|
||||
append(currIndent);
|
||||
@@ -378,10 +380,9 @@ public final class VmValueRenderer {
|
||||
|
||||
contexts.pop();
|
||||
append(interiorNewline);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
if (!isEmpty.get()) {
|
||||
if (!isEmpty) {
|
||||
// replace last interiorNewline with leadingOrTrailingNewline
|
||||
builder.delete(builder.length() - interiorNewline.length(), builder.length());
|
||||
append(leadingOrTrailingNewline);
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.pkl.core.runtime.VmListing;
|
||||
import org.pkl.core.runtime.VmMap;
|
||||
import org.pkl.core.runtime.VmMapping;
|
||||
import org.pkl.core.runtime.VmNull;
|
||||
import org.pkl.core.runtime.VmObjectCursor.CursorOption;
|
||||
import org.pkl.core.runtime.VmReference;
|
||||
import org.pkl.core.runtime.VmSet;
|
||||
import org.pkl.core.runtime.VmTypeAlias;
|
||||
@@ -69,6 +70,9 @@ public abstract class AbstractRenderer implements VmValueVisitor {
|
||||
/** The (closest) {@link SourceSection} of the value being visited, for better error messages. */
|
||||
protected @Nullable SourceSection currSourceSection = null;
|
||||
|
||||
/** Whether we are visiting an entry key or not. */
|
||||
private boolean isVisitingEntryKey;
|
||||
|
||||
protected AbstractRenderer(
|
||||
String name, PklConverter converter, boolean skipNullProperties, boolean skipNullEntries) {
|
||||
this.name = name;
|
||||
@@ -118,9 +122,9 @@ public abstract class AbstractRenderer implements VmValueVisitor {
|
||||
try {
|
||||
VmValueVisitor.super.visit(value);
|
||||
} catch (VmUndefinedValueException e) {
|
||||
// Only fill in the path if rendering the leaf member that threw the error. We know we are
|
||||
// at a leaf if the error's receiver is the same as `value`.
|
||||
if (e.getReceiver() == value) {
|
||||
// Only fill in a hint if we are not visiting an entry key; when visiting an entry key,
|
||||
// `currPath` is not tracked from the actual root value (see `visitEntryKeyValue`).
|
||||
if (e.getReceiver() == value && !isVisitingEntryKey) {
|
||||
throw e.fillInHint(currPath, topLevelValue);
|
||||
}
|
||||
throw e;
|
||||
@@ -193,18 +197,17 @@ public abstract class AbstractRenderer implements VmValueVisitor {
|
||||
enclosingValue = value;
|
||||
var isFirst = new MutableBoolean(true);
|
||||
|
||||
value.iterateAlreadyForcedMemberValues(
|
||||
(memberKey, member, memberValue) -> {
|
||||
if (member.isClass() || member.isTypeAlias()) return true;
|
||||
for (var cursor = value.properties(CursorOption.ALL_VALUES); cursor.advance(); ) {
|
||||
var member = cursor.member();
|
||||
if (member.isClass() || member.isTypeAlias()) continue;
|
||||
assert member.isProp();
|
||||
doVisitProperty(
|
||||
(Identifier) memberKey,
|
||||
memberValue,
|
||||
value.getVmClass().getProperty((Identifier) memberKey),
|
||||
member.getSourceSection(),
|
||||
(Identifier) cursor.key(),
|
||||
cursor.cachedValue(),
|
||||
value.getVmClass().getProperty((Identifier) cursor.key()),
|
||||
cursor.member().getSourceSection(),
|
||||
isFirst);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
enclosingValue = prevEnclosingValue;
|
||||
endTyped(value, isFirst.get());
|
||||
@@ -220,21 +223,20 @@ public abstract class AbstractRenderer implements VmValueVisitor {
|
||||
var isFirst = new MutableBoolean(true);
|
||||
var canRenderPropertyOrEntry = canRenderPropertyOrEntryOf(value);
|
||||
|
||||
value.iterateAlreadyForcedMemberValues(
|
||||
(memberKey, member, memberValue) -> {
|
||||
var sourceSection = member.getSourceSection();
|
||||
if (member.isProp()) {
|
||||
for (var cursor = value.members(CursorOption.ALL_VALUES); cursor.advance(); ) {
|
||||
var memberKey = cursor.key();
|
||||
var memberValue = cursor.cachedValue();
|
||||
var sourceSection = cursor.member().getSourceSection();
|
||||
if (cursor.isProperty()) {
|
||||
if (!canRenderPropertyOrEntry) cannotRenderObjectWithElementsAndOtherMembers(value);
|
||||
doVisitProperty((Identifier) memberKey, memberValue, null, sourceSection, isFirst);
|
||||
} else if (member.isEntry()) {
|
||||
} else if (cursor.isEntry()) {
|
||||
if (!canRenderPropertyOrEntry) cannotRenderObjectWithElementsAndOtherMembers(value);
|
||||
doVisitEntry(memberKey, memberValue, sourceSection, isFirst);
|
||||
} else {
|
||||
doVisitElement((long) memberKey, memberValue, sourceSection, isFirst.getAndSetFalse());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
}
|
||||
enclosingValue = prevEnclosingValue;
|
||||
endDynamic(value, isFirst.get());
|
||||
}
|
||||
@@ -259,13 +261,13 @@ public abstract class AbstractRenderer implements VmValueVisitor {
|
||||
enclosingValue = value;
|
||||
var isFirst = new MutableBoolean(true);
|
||||
|
||||
value.iterateAlreadyForcedMemberValues(
|
||||
(memberKey, member, memberValue) -> {
|
||||
assert member.isElement();
|
||||
for (var cursor = value.elements(CursorOption.ALL_VALUES); cursor.advance(); ) {
|
||||
doVisitElement(
|
||||
(long) memberKey, memberValue, member.getSourceSection(), isFirst.getAndSetFalse());
|
||||
return true;
|
||||
});
|
||||
(long) cursor.key(),
|
||||
cursor.cachedValue(),
|
||||
cursor.member().getSourceSection(),
|
||||
isFirst.getAndSetFalse());
|
||||
}
|
||||
|
||||
enclosingValue = prevEnclosingValue;
|
||||
endListing(value, isFirst.get());
|
||||
@@ -280,12 +282,10 @@ public abstract class AbstractRenderer implements VmValueVisitor {
|
||||
enclosingValue = value;
|
||||
var isFirst = new MutableBoolean(true);
|
||||
|
||||
value.iterateAlreadyForcedMemberValues(
|
||||
(memberKey, member, memberValue) -> {
|
||||
assert member.isEntry();
|
||||
doVisitEntry(memberKey, memberValue, member.getSourceSection(), isFirst);
|
||||
return true;
|
||||
});
|
||||
for (var cursor = value.entries(CursorOption.ALL_VALUES); cursor.advance(); ) {
|
||||
assert cursor.member().isEntry();
|
||||
doVisitEntry(cursor.key(), cursor.cachedValue(), cursor.member().getSourceSection(), isFirst);
|
||||
}
|
||||
|
||||
enclosingValue = prevEnclosingValue;
|
||||
endMapping(value, isFirst.get());
|
||||
@@ -380,7 +380,12 @@ public abstract class AbstractRenderer implements VmValueVisitor {
|
||||
protected void visitEntryKeyValue(
|
||||
Object key, boolean isFirst, Deque<Object> valuePath, Object value) {
|
||||
currPath = new ArrayDeque<>();
|
||||
isVisitingEntryKey = true;
|
||||
try {
|
||||
visitEntryKey(key, isFirst);
|
||||
} finally {
|
||||
isVisitingEntryKey = false;
|
||||
}
|
||||
currPath = valuePath;
|
||||
visitEntryValue(value);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,7 +28,7 @@ public abstract class ExternalMemberNode extends ExpressionNode {
|
||||
super();
|
||||
}
|
||||
|
||||
protected abstract ExpressionNode getReceiverNode();
|
||||
public abstract ExpressionNode getReceiverNode();
|
||||
|
||||
@TruffleBoundary
|
||||
protected final String getQualifiedMemberName() {
|
||||
|
||||
@@ -15,10 +15,12 @@
|
||||
*/
|
||||
package org.pkl.core.stdlib;
|
||||
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import java.util.*;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.pkl.core.ast.member.ClassProperty;
|
||||
import org.pkl.core.runtime.*;
|
||||
import org.pkl.core.runtime.VmObjectCursor.CursorOption;
|
||||
import org.pkl.core.util.Pair;
|
||||
|
||||
public final class PklConverter implements VmValueConverter<Object> {
|
||||
@@ -49,13 +51,16 @@ public final class PklConverter implements VmValueConverter<Object> {
|
||||
private final @Nullable VmFunction referenceConverter;
|
||||
|
||||
private PklConverter(
|
||||
VmMapping converters, VmMapping convertPropertyTransformers, Object rendererOrParser) {
|
||||
VmMapping converters,
|
||||
VmMapping convertPropertyTransformers,
|
||||
Object rendererOrParser,
|
||||
IndirectCallNode callNode) {
|
||||
converters.force(false, false);
|
||||
convertPropertyTransformers.force(false, false);
|
||||
typeConverters = createTypeConverters(converters);
|
||||
typeConverters = createTypeConverters(converters, callNode);
|
||||
this.convertPropertyTransformers =
|
||||
createConvertPropertyTransformers(convertPropertyTransformers);
|
||||
pathConverters = createPathConverters(converters);
|
||||
createConvertPropertyTransformers(convertPropertyTransformers, callNode);
|
||||
pathConverters = createPathConverters(converters, callNode);
|
||||
this.rendererOrParser = rendererOrParser;
|
||||
|
||||
stringConverter = typeConverters.get(BaseModule.getStringClass());
|
||||
@@ -81,19 +86,23 @@ public final class PklConverter implements VmValueConverter<Object> {
|
||||
}
|
||||
|
||||
public static final PklConverter NOOP =
|
||||
new PklConverter(VmMapping.empty(), VmMapping.empty(), VmNull.withoutDefault());
|
||||
new PklConverter(
|
||||
VmMapping.empty(),
|
||||
VmMapping.empty(),
|
||||
VmNull.withoutDefault(),
|
||||
IndirectCallNode.getUncached());
|
||||
|
||||
public static PklConverter fromRenderer(VmTyped renderer) {
|
||||
var converters = (VmMapping) VmUtils.readMember(renderer, Identifier.CONVERTERS);
|
||||
public static PklConverter fromRenderer(VmTyped renderer, IndirectCallNode callNode) {
|
||||
var converters = (VmMapping) VmUtils.readMember(renderer, Identifier.CONVERTERS, callNode);
|
||||
var convertPropertyTransformers =
|
||||
(VmMapping) VmUtils.readMember(renderer, Identifier.CONVERT_PROPERTY_TRANSFORMERS);
|
||||
return new PklConverter(converters, convertPropertyTransformers, renderer);
|
||||
return new PklConverter(converters, convertPropertyTransformers, renderer, callNode);
|
||||
}
|
||||
|
||||
public static PklConverter fromParser(VmTyped parser) {
|
||||
var converters = (VmMapping) VmUtils.readMember(parser, Identifier.CONVERTERS);
|
||||
public static PklConverter fromParser(VmTyped parser, IndirectCallNode callNode) {
|
||||
var converters = (VmMapping) VmUtils.readMember(parser, Identifier.CONVERTERS, callNode);
|
||||
return new PklConverter(
|
||||
converters, VmMapping.empty(), parser); // no annotation converters in parsers
|
||||
converters, VmMapping.empty(), parser, callNode); // no annotation converters in parsers
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -234,44 +243,38 @@ public final class PklConverter implements VmValueConverter<Object> {
|
||||
return Pair.of(Identifier.get((String) prop.getFirst()), prop.getSecond());
|
||||
}
|
||||
|
||||
private Map<VmClass, VmFunction> createTypeConverters(VmMapping converters) {
|
||||
private Map<VmClass, VmFunction> createTypeConverters(
|
||||
VmMapping converters, IndirectCallNode callNode) {
|
||||
var result = new HashMap<VmClass, VmFunction>();
|
||||
converters.iterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
assert value != null; // forced in ctor
|
||||
if (key instanceof VmClass vmClass) {
|
||||
result.put(vmClass, (VmFunction) value);
|
||||
for (var cursor = converters.entries(CursorOption.ANY_ORDER); cursor.advance(); ) {
|
||||
if (cursor.key() instanceof VmClass vmClass) {
|
||||
result.put(vmClass, (VmFunction) cursor.value(callNode));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<VmClass, VmFunction> createConvertPropertyTransformers(
|
||||
VmMapping convertPropertyTransformers) {
|
||||
VmMapping convertPropertyTransformers, IndirectCallNode callNode) {
|
||||
var result = new HashMap<VmClass, VmFunction>();
|
||||
convertPropertyTransformers.iterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
assert value != null; // forced in ctor
|
||||
result.put((VmClass) key, ((VmFunction) value));
|
||||
return true;
|
||||
});
|
||||
|
||||
for (var cursor = convertPropertyTransformers.members(CursorOption.ALL_VALUES);
|
||||
cursor.advance(); ) {
|
||||
var value = cursor.value(callNode);
|
||||
result.put((VmClass) cursor.key(), (VmFunction) value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Pair<Object[], VmFunction>[] createPathConverters(VmMapping converters) {
|
||||
private Pair<Object[], VmFunction>[] createPathConverters(
|
||||
VmMapping converters, IndirectCallNode callNode) {
|
||||
var result = new ArrayList<Pair<Object[], VmFunction>>();
|
||||
var parser = new PathSpecParser();
|
||||
converters.iterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
assert value != null; // forced in ctor
|
||||
if (key instanceof String string) {
|
||||
result.add(Pair.of(parser.parse(string), (VmFunction) value));
|
||||
for (var cursor = converters.entries(); cursor.advance(); ) {
|
||||
if (cursor.key() instanceof String string) {
|
||||
result.add(Pair.of(parser.parse(string), (VmFunction) cursor.value(callNode)));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return result.toArray(new Pair[0]);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,9 +16,13 @@
|
||||
package org.pkl.core.stdlib.base;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import org.pkl.core.ast.internal.ReadCursorValueNode;
|
||||
import org.pkl.core.runtime.*;
|
||||
import org.pkl.core.runtime.VmObjectCursor.CursorOption;
|
||||
import org.pkl.core.stdlib.ExternalMethod0Node;
|
||||
import org.pkl.core.stdlib.ExternalMethod1Node;
|
||||
|
||||
@@ -65,30 +69,33 @@ public final class DynamicNodes {
|
||||
|
||||
public abstract static class toMap extends ExternalMethod0Node {
|
||||
@Specialization
|
||||
protected VmMap eval(VmDynamic self) {
|
||||
protected VmMap eval(
|
||||
VirtualFrame frame,
|
||||
VmDynamic self,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
var builder = VmMap.builder();
|
||||
self.forceAndIterateMemberValues( // could be smarter and skip forcing elements
|
||||
(key, member, value) -> {
|
||||
if (!member.isElement()) {
|
||||
builder.add(key instanceof Identifier ? key.toString() : key, value);
|
||||
for (var cursor = self.members(); cursor.advance(); ) {
|
||||
if (!cursor.isElement()) {
|
||||
var key = cursor.key();
|
||||
builder.add(
|
||||
key instanceof Identifier ? key.toString() : key,
|
||||
readCursorValueNode.execute(frame, cursor));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class toList extends ExternalMethod0Node {
|
||||
@Specialization
|
||||
protected VmList eval(VmDynamic self) {
|
||||
protected VmList eval(
|
||||
VirtualFrame frame,
|
||||
VmDynamic self,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
var builder = VmList.EMPTY.builder();
|
||||
self.forceAndIterateMemberValues( // could be smarter and only force elements
|
||||
(key, member, value) -> {
|
||||
if (member.isElement()) {
|
||||
builder.add(value);
|
||||
for (var cursor = self.elements(CursorOption.ALL_VALUES); cursor.advance(); ) {
|
||||
builder.add(readCursorValueNode.execute(frame, cursor));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
package org.pkl.core.stdlib.base;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import org.pkl.core.runtime.*;
|
||||
import org.pkl.core.stdlib.AbstractStringRenderer;
|
||||
import org.pkl.core.stdlib.ExternalMethod1Node;
|
||||
@@ -29,12 +31,13 @@ public final class JsonRendererNodes {
|
||||
public abstract static class renderDocument extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected String eval(VmTyped self, Object value) {
|
||||
protected String eval(
|
||||
VmTyped self, Object value, @Cached("create()") IndirectCallNode callNode) {
|
||||
// JSON document can have any top-level value
|
||||
// https://stackoverflow.com/a/3833312
|
||||
// http://www.ietf.org/rfc/rfc7159.txt
|
||||
var builder = new StringBuilder();
|
||||
createRenderer(self, builder).renderDocument(value);
|
||||
createRenderer(self, builder, callNode).renderDocument(value);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -42,17 +45,20 @@ public final class JsonRendererNodes {
|
||||
public abstract static class renderValue extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected String eval(VmTyped self, Object value) {
|
||||
protected String eval(
|
||||
VmTyped self, Object value, @Cached("create()") IndirectCallNode callNode) {
|
||||
var builder = new StringBuilder();
|
||||
createRenderer(self, builder).renderValue(value);
|
||||
createRenderer(self, builder, callNode).renderValue(value);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static JsonRenderer createRenderer(VmTyped self, StringBuilder builder) {
|
||||
private static JsonRenderer createRenderer(
|
||||
VmTyped self, StringBuilder builder, IndirectCallNode callNode) {
|
||||
var indent = (String) VmUtils.readMember(self, Identifier.INDENT);
|
||||
var omitNullProperties = (boolean) VmUtils.readMember(self, Identifier.OMIT_NULL_PROPERTIES);
|
||||
return new JsonRenderer(builder, indent, PklConverter.fromRenderer(self), omitNullProperties);
|
||||
return new JsonRenderer(
|
||||
builder, indent, PklConverter.fromRenderer(self, callNode), omitNullProperties);
|
||||
}
|
||||
|
||||
private static final class JsonRenderer extends AbstractStringRenderer {
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.pkl.core.stdlib.base;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.LoopNode;
|
||||
@@ -25,6 +26,7 @@ import org.jspecify.annotations.Nullable;
|
||||
import org.pkl.core.ast.expression.binary.*;
|
||||
import org.pkl.core.ast.internal.IsInstanceOfNode;
|
||||
import org.pkl.core.ast.internal.IsInstanceOfNodeGen;
|
||||
import org.pkl.core.ast.internal.ReadCursorValueNode;
|
||||
import org.pkl.core.ast.internal.ToStringNode;
|
||||
import org.pkl.core.ast.internal.ToStringNodeGen;
|
||||
import org.pkl.core.ast.lambda.*;
|
||||
@@ -651,8 +653,11 @@ public final class ListNodes {
|
||||
|
||||
public abstract static class flatten extends ExternalMethod0Node {
|
||||
@Specialization
|
||||
protected VmList eval(VmList self) {
|
||||
return (VmList) self.flatten();
|
||||
protected VmList eval(
|
||||
VirtualFrame frame,
|
||||
VmList self,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
return (VmList) self.flatten(frame, readCursorValueNode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1285,8 +1290,8 @@ public final class ListNodes {
|
||||
var iter = self.iterator();
|
||||
var builder = VmUtils.createBuilder();
|
||||
VmUtils.appendToBuilder(builder, toStringNode.executeWith(frame, iter.next()));
|
||||
|
||||
while (loopConditionProfile.profile(iter.hasNext())) {
|
||||
loopConditionProfile.profileCounted(self.getLength());
|
||||
while (loopConditionProfile.inject(iter.hasNext())) {
|
||||
VmUtils.appendToBuilder(builder, separator);
|
||||
VmUtils.appendToBuilder(builder, toStringNode.executeWith(frame, iter.next()));
|
||||
}
|
||||
|
||||
@@ -17,15 +17,18 @@ package org.pkl.core.stdlib.base;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import com.oracle.truffle.api.nodes.LoopNode;
|
||||
import org.pkl.core.ast.PklNode;
|
||||
import org.pkl.core.ast.internal.ReadCursorValueNode;
|
||||
import org.pkl.core.ast.internal.ToStringNode;
|
||||
import org.pkl.core.ast.internal.ToStringNodeGen;
|
||||
import org.pkl.core.ast.lambda.*;
|
||||
import org.pkl.core.runtime.*;
|
||||
import org.pkl.core.runtime.VmObjectCursor.CursorOption;
|
||||
import org.pkl.core.stdlib.ExternalMethod0Node;
|
||||
import org.pkl.core.stdlib.ExternalMethod1Node;
|
||||
import org.pkl.core.stdlib.ExternalMethod2Node;
|
||||
@@ -65,11 +68,12 @@ public final class ListingNodes {
|
||||
|
||||
public abstract static class getOrNull extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
protected Object eval(VmListing self, long index) {
|
||||
protected Object eval(
|
||||
VmListing self, long index, @Cached("create()") IndirectCallNode callNode) {
|
||||
if (index < 0 || index >= self.getLength()) {
|
||||
return VmNull.withoutDefault();
|
||||
}
|
||||
return VmUtils.readMember(self, index);
|
||||
return VmUtils.readMember(self, index, callNode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,16 +87,24 @@ public final class ListingNodes {
|
||||
var defaultFunction = (VmFunction) VmUtils.readMember(self, Identifier.DEFAULT, callNode);
|
||||
return applyNode.execute(defaultFunction, index);
|
||||
}
|
||||
return VmUtils.readMember(self, index);
|
||||
return VmUtils.readMember(self, index, callNode);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class isDistinct extends ExternalPropertyNode {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected boolean eval(VmListing self) {
|
||||
var visitedValues = CollectionUtils.newHashSet();
|
||||
return self.forceAndIterateMemberValues((key, member, value) -> visitedValues.add(value));
|
||||
protected boolean eval(
|
||||
VirtualFrame frame,
|
||||
VmListing self,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
var seenValues = EconomicSets.create();
|
||||
for (var cursor = self.elements(CursorOption.ANY_ORDER); cursor.advance(); ) {
|
||||
var value = readCursorValueNode.execute(frame, cursor);
|
||||
if (!EconomicSets.add(seenValues, value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,80 +112,90 @@ public final class ListingNodes {
|
||||
@Child private ApplyVmFunction1Node applyNode = ApplyVmFunction1Node.create();
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(VmListing self, VmFunction selector) {
|
||||
var visitedValues = CollectionUtils.newHashSet();
|
||||
return self.forceAndIterateMemberValues(
|
||||
(key, member, value) -> visitedValues.add(applyNode.execute(selector, value)));
|
||||
protected boolean eval(
|
||||
VirtualFrame frame,
|
||||
VmListing self,
|
||||
VmFunction selector,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
var seenValues = EconomicSets.create();
|
||||
for (var cursor = self.elements(CursorOption.ANY_ORDER); cursor.advance(); ) {
|
||||
var cursorValue = readCursorValueNode.execute(frame, cursor);
|
||||
var value = applyNode.execute(selector, cursorValue);
|
||||
if (!EconomicSets.add(seenValues, value)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class distinct extends ExternalPropertyNode {
|
||||
@Specialization
|
||||
protected VmListing eval(VmListing self) {
|
||||
var visitedValues = CollectionUtils.newHashSet();
|
||||
protected VmListing eval(
|
||||
VirtualFrame frame,
|
||||
VmListing self,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
var seenValues = EconomicSets.create();
|
||||
var builder = new VmObjectBuilder();
|
||||
|
||||
self.forceAndIterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
if (visitedValues.add(value)) {
|
||||
for (var cursor = self.elements(CursorOption.ALL_VALUES); cursor.advance(); ) {
|
||||
var value = readCursorValueNode.execute(frame, cursor);
|
||||
if (EconomicSets.add(seenValues, value)) {
|
||||
builder.addElement(value);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
}
|
||||
return builder.toListing();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class first extends ExternalPropertyNode {
|
||||
@Specialization
|
||||
protected Object eval(VmListing self) {
|
||||
protected Object eval(VmListing self, @Cached("create()") IndirectCallNode callNode) {
|
||||
checkNonEmpty(self, this);
|
||||
return VmUtils.readMember(self, 0L);
|
||||
return VmUtils.readMember(self, 0L, callNode);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class firstOrNull extends ExternalPropertyNode {
|
||||
@Specialization
|
||||
protected Object eval(VmListing self) {
|
||||
protected Object eval(VmListing self, @Cached("create()") IndirectCallNode callNode) {
|
||||
if (self.isEmpty()) {
|
||||
return VmNull.withoutDefault();
|
||||
}
|
||||
return VmUtils.readMember(self, 0L);
|
||||
return VmUtils.readMember(self, 0L, callNode);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class last extends ExternalPropertyNode {
|
||||
@Specialization
|
||||
protected Object eval(VmListing self) {
|
||||
protected Object eval(VmListing self, @Cached("create()") IndirectCallNode callNode) {
|
||||
checkNonEmpty(self, this);
|
||||
return VmUtils.readMember(self, self.getLength() - 1L);
|
||||
return VmUtils.readMember(self, self.getLength() - 1L, callNode);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class lastOrNull extends ExternalPropertyNode {
|
||||
@Specialization
|
||||
protected Object eval(VmListing self) {
|
||||
protected Object eval(VmListing self, @Cached("create()") IndirectCallNode callNode) {
|
||||
var length = self.getLength();
|
||||
return length == 0 ? VmNull.withoutDefault() : VmUtils.readMember(self, length - 1L);
|
||||
return length == 0
|
||||
? VmNull.withoutDefault()
|
||||
: VmUtils.readMember(self, length - 1L, callNode);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class single extends ExternalPropertyNode {
|
||||
@Specialization
|
||||
protected Object eval(VmListing self) {
|
||||
protected Object eval(VmListing self, @Cached("create()") IndirectCallNode callNode) {
|
||||
checkSingleton(self, this);
|
||||
return VmUtils.readMember(self, 0L);
|
||||
return VmUtils.readMember(self, 0L, callNode);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class singleOrNull extends ExternalPropertyNode {
|
||||
@Specialization
|
||||
protected Object eval(VmListing self) {
|
||||
protected Object eval(VmListing self, @Cached("create()") IndirectCallNode callNode) {
|
||||
if (self.getLength() != 1) {
|
||||
return VmNull.withoutDefault();
|
||||
}
|
||||
return VmUtils.readMember(self, 0L);
|
||||
return VmUtils.readMember(self, 0L, callNode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,18 +203,20 @@ public final class ListingNodes {
|
||||
@Child private ApplyVmFunction1Node applyNode = ApplyVmFunction1Node.create();
|
||||
|
||||
@Specialization
|
||||
protected VmListing eval(VmListing self, VmFunction selector) {
|
||||
var visitedValues = CollectionUtils.newHashSet();
|
||||
protected VmListing eval(
|
||||
VirtualFrame frame,
|
||||
VmListing self,
|
||||
VmFunction selector,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
var seenValues = EconomicSets.create();
|
||||
var builder = new VmObjectBuilder();
|
||||
|
||||
self.forceAndIterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
if (visitedValues.add(applyNode.execute(selector, value))) {
|
||||
for (var cursor = self.elements(CursorOption.ALL_VALUES); cursor.advance(); ) {
|
||||
var value = readCursorValueNode.execute(frame, cursor);
|
||||
var selectorValue = applyNode.execute(selector, value);
|
||||
if (EconomicSets.add(seenValues, selectorValue)) {
|
||||
builder.addElement(value);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
}
|
||||
return builder.toListing();
|
||||
}
|
||||
}
|
||||
@@ -201,17 +225,16 @@ public final class ListingNodes {
|
||||
@Child private ApplyVmFunction1Node applyNode = ApplyVmFunction1Node.create();
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(VmListing self, VmFunction predicate) {
|
||||
var result = new MutableBoolean(true);
|
||||
self.iterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
if (value == null) {
|
||||
value = VmUtils.readMember(self, key);
|
||||
protected boolean eval(
|
||||
VirtualFrame frame,
|
||||
VmListing self,
|
||||
VmFunction predicate,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
for (var cursor = self.elements(CursorOption.ANY_ORDER); cursor.advance(); ) {
|
||||
var value = readCursorValueNode.execute(frame, cursor);
|
||||
if (!applyNode.executeBoolean(predicate, value)) return false;
|
||||
}
|
||||
result.set(applyNode.executeBoolean(predicate, value));
|
||||
return result.get();
|
||||
});
|
||||
return result.get();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,34 +242,39 @@ public final class ListingNodes {
|
||||
@Child private ApplyVmFunction1Node applyNode = ApplyVmFunction1Node.create();
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(VmListing self, VmFunction predicate) {
|
||||
var result = new MutableBoolean(false);
|
||||
self.iterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
if (value == null) {
|
||||
value = VmUtils.readMember(self, key);
|
||||
protected boolean eval(
|
||||
VirtualFrame frame,
|
||||
VmListing self,
|
||||
VmFunction predicate,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
for (var cursor = self.elements(CursorOption.ANY_ORDER); cursor.advance(); ) {
|
||||
var value = readCursorValueNode.execute(frame, cursor);
|
||||
if (applyNode.executeBoolean(predicate, value)) return true;
|
||||
}
|
||||
result.set(applyNode.executeBoolean(predicate, value));
|
||||
return !result.get();
|
||||
});
|
||||
return result.get();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class contains extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
protected boolean eval(VmListing self, Object element) {
|
||||
var result = new MutableBoolean(false);
|
||||
self.iterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
if (value == null) {
|
||||
value = VmUtils.readMember(self, key);
|
||||
protected boolean eval(
|
||||
VirtualFrame frame,
|
||||
VmListing self,
|
||||
Object element,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
for (var cursor = self.elements(CursorOption.ANY_ORDER); cursor.advance(); ) {
|
||||
var value = readCursorValueNode.execute(frame, cursor);
|
||||
// TODO replace with EqualNode
|
||||
if (equals(value, element)) {
|
||||
return true;
|
||||
}
|
||||
result.set(element.equals(value));
|
||||
return !result.get();
|
||||
});
|
||||
LoopNode.reportLoopCount(this, self.getLength());
|
||||
return result.get();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
private boolean equals(Object a, Object b) {
|
||||
return a.equals(b);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,15 +282,19 @@ public final class ListingNodes {
|
||||
@Child private ApplyVmFunction2Node applyLambdaNode = ApplyVmFunction2NodeGen.create();
|
||||
|
||||
@Specialization
|
||||
protected Object eval(VmListing self, Object initial, VmFunction function) {
|
||||
var result = new MutableReference<>(initial);
|
||||
self.forceAndIterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
result.set(applyLambdaNode.execute(function, result.get(), value));
|
||||
return true;
|
||||
});
|
||||
protected Object eval(
|
||||
VirtualFrame frame,
|
||||
VmListing self,
|
||||
Object initial,
|
||||
VmFunction function,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
var result = initial;
|
||||
for (var cursor = self.elements(CursorOption.ALL_VALUES); cursor.advance(); ) {
|
||||
var value = readCursorValueNode.execute(frame, cursor);
|
||||
result = applyLambdaNode.execute(function, result, value);
|
||||
}
|
||||
LoopNode.reportLoopCount(this, self.getLength());
|
||||
return result.get();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,17 +302,19 @@ public final class ListingNodes {
|
||||
@Child private ApplyVmFunction3Node applyLambdaNode = ApplyVmFunction3NodeGen.create();
|
||||
|
||||
@Specialization
|
||||
protected Object eval(VmListing self, Object initial, VmFunction function) {
|
||||
var index = new MutableLong(0);
|
||||
var result = new MutableReference<>(initial);
|
||||
self.forceAndIterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
result.set(
|
||||
applyLambdaNode.execute(function, index.getAndIncrement(), result.get(), value));
|
||||
return true;
|
||||
});
|
||||
protected Object eval(
|
||||
VirtualFrame frame,
|
||||
VmListing self,
|
||||
Object initial,
|
||||
VmFunction function,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
var result = initial;
|
||||
for (var cursor = self.elements(CursorOption.ALL_VALUES); cursor.advance(); ) {
|
||||
var value = readCursorValueNode.execute(frame, cursor);
|
||||
result = applyLambdaNode.execute(function, cursor.key(), result, value);
|
||||
}
|
||||
LoopNode.reportLoopCount(this, self.getLength());
|
||||
return result.get();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,21 +322,25 @@ public final class ListingNodes {
|
||||
@Child ToStringNode toStringNode = ToStringNodeGen.create(sourceSection, null);
|
||||
|
||||
@Specialization
|
||||
protected Object eval(VirtualFrame frame, VmListing self, String separator) {
|
||||
protected Object eval(
|
||||
VirtualFrame frame,
|
||||
VmListing self,
|
||||
String separator,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
if (self.isEmpty()) return "";
|
||||
|
||||
var builder = new StringBuilder();
|
||||
var materializedFrame = frame.materialize();
|
||||
self.forceAndIterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
if (!key.equals(0L)) {
|
||||
builder.append(separator);
|
||||
var isFirst = true;
|
||||
for (var cursor = self.elements(); cursor.advance(); ) {
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
} else {
|
||||
VmUtils.appendToBuilder(builder, separator);
|
||||
}
|
||||
var value = toStringNode.executeWith(frame, readCursorValueNode.execute(frame, cursor));
|
||||
VmUtils.appendToBuilder(builder, value);
|
||||
}
|
||||
builder.append(toStringNode.executeWith(materializedFrame, value));
|
||||
return true;
|
||||
});
|
||||
LoopNode.reportLoopCount(this, self.getLength());
|
||||
return builder.toString();
|
||||
return VmUtils.builderToString(builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -313,26 +351,30 @@ public final class ListingNodes {
|
||||
|
||||
public abstract static class toList extends ExternalMethod0Node {
|
||||
@Specialization
|
||||
protected VmList eval(VmListing self) {
|
||||
protected VmList eval(
|
||||
VirtualFrame frame,
|
||||
VmListing self,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
var builder = VmList.EMPTY.builder();
|
||||
self.forceAndIterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
for (var cursor = self.elements(CursorOption.ALL_VALUES); cursor.advance(); ) {
|
||||
var value = readCursorValueNode.execute(frame, cursor);
|
||||
builder.add(value);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class toSet extends ExternalMethod0Node {
|
||||
@Specialization
|
||||
protected VmSet eval(VmListing self) {
|
||||
protected VmSet eval(
|
||||
VirtualFrame frame,
|
||||
VmListing self,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
var builder = VmSet.EMPTY.builder();
|
||||
self.forceAndIterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
for (var cursor = self.elements(CursorOption.ALL_VALUES); cursor.advance(); ) {
|
||||
var value = readCursorValueNode.execute(frame, cursor);
|
||||
builder.add(value);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,20 +15,23 @@
|
||||
*/
|
||||
package org.pkl.core.stdlib.base;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import org.pkl.core.ast.internal.ReadCursorValueNode;
|
||||
import org.pkl.core.ast.lambda.ApplyVmFunction1Node;
|
||||
import org.pkl.core.ast.lambda.ApplyVmFunction2Node;
|
||||
import org.pkl.core.ast.lambda.ApplyVmFunction2NodeGen;
|
||||
import org.pkl.core.ast.lambda.ApplyVmFunction3Node;
|
||||
import org.pkl.core.ast.lambda.ApplyVmFunction3NodeGen;
|
||||
import org.pkl.core.runtime.*;
|
||||
import org.pkl.core.runtime.VmObjectCursor.CursorOption;
|
||||
import org.pkl.core.stdlib.ExternalMethod0Node;
|
||||
import org.pkl.core.stdlib.ExternalMethod1Node;
|
||||
import org.pkl.core.stdlib.ExternalMethod2Node;
|
||||
import org.pkl.core.stdlib.ExternalPropertyNode;
|
||||
import org.pkl.core.util.MutableBoolean;
|
||||
import org.pkl.core.util.MutableReference;
|
||||
|
||||
public final class MappingNodes {
|
||||
private MappingNodes() {}
|
||||
@@ -66,7 +69,7 @@ public final class MappingNodes {
|
||||
protected boolean eval(VmMapping self, Object key) {
|
||||
if (self.hasCachedValue(key)) return true;
|
||||
|
||||
for (VmObjectLike curr = self; curr != null; curr = curr.getParent()) {
|
||||
for (VmObject curr = self; curr != null; curr = curr.getParent()) {
|
||||
if (curr.hasMember(key)) return true;
|
||||
}
|
||||
|
||||
@@ -76,17 +79,29 @@ public final class MappingNodes {
|
||||
|
||||
public abstract static class containsValue extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
protected boolean eval(VmMapping self, Object value) {
|
||||
var foundValue = new MutableBoolean(false);
|
||||
self.iterateMemberValues(
|
||||
(key, member, memberValue) -> {
|
||||
if (memberValue == null) {
|
||||
memberValue = VmUtils.readMember(self, key);
|
||||
protected boolean eval(
|
||||
VirtualFrame frame,
|
||||
VmMapping self,
|
||||
Object value,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
for (var cursor = self.entries(CursorOption.ANY_ORDER); cursor.advance(); ) {
|
||||
var cursorValue = readCursorValueNode.execute(frame, cursor);
|
||||
// TODO replace with EqualNode
|
||||
if (equals(value, cursorValue)) {
|
||||
return true;
|
||||
}
|
||||
foundValue.set(value.equals(memberValue));
|
||||
return !foundValue.get();
|
||||
});
|
||||
return foundValue.get();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
private boolean equals(Object a, Object b) {
|
||||
return a.equals(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInstrumentable() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,14 +134,19 @@ public final class MappingNodes {
|
||||
@Child private ApplyVmFunction3Node applyLambdaNode = ApplyVmFunction3NodeGen.create();
|
||||
|
||||
@Specialization
|
||||
protected Object eval(VmMapping self, Object initial, VmFunction function) {
|
||||
var result = new MutableReference<>(initial);
|
||||
self.forceAndIterateMemberValues(
|
||||
(key, def, value) -> {
|
||||
result.set(applyLambdaNode.execute(function, result.get(), key, value));
|
||||
return true;
|
||||
});
|
||||
return result.get();
|
||||
protected Object eval(
|
||||
VirtualFrame frame,
|
||||
VmMapping self,
|
||||
Object initial,
|
||||
VmFunction function,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
var result = initial;
|
||||
for (var cursor = self.entries(); cursor.advance(); ) {
|
||||
result =
|
||||
applyLambdaNode.execute(
|
||||
function, result, cursor.key(), readCursorValueNode.execute(frame, cursor));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,17 +154,16 @@ public final class MappingNodes {
|
||||
@Child private ApplyVmFunction2Node applyLambdaNode = ApplyVmFunction2NodeGen.create();
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(VmMapping self, VmFunction function) {
|
||||
var result = new MutableBoolean(true);
|
||||
self.iterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
if (value == null) {
|
||||
value = VmUtils.readMember(self, key);
|
||||
protected boolean eval(
|
||||
VirtualFrame frame,
|
||||
VmMapping self,
|
||||
VmFunction function,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
for (var cursor = self.entries(CursorOption.ANY_ORDER); cursor.advance(); ) {
|
||||
if (!applyLambdaNode.executeBoolean(
|
||||
function, cursor.key(), readCursorValueNode.execute(frame, cursor))) return false;
|
||||
}
|
||||
result.set(applyLambdaNode.executeBoolean(function, key, value));
|
||||
return result.get();
|
||||
});
|
||||
return result.get();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,29 +171,31 @@ public final class MappingNodes {
|
||||
@Child private ApplyVmFunction2Node applyLambdaNode = ApplyVmFunction2NodeGen.create();
|
||||
|
||||
@Specialization
|
||||
protected boolean eval(VmMapping self, VmFunction function) {
|
||||
var result = new MutableBoolean(false);
|
||||
self.iterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
if (value == null) {
|
||||
value = VmUtils.readMember(self, key);
|
||||
protected boolean eval(
|
||||
VirtualFrame frame,
|
||||
VmMapping self,
|
||||
VmFunction function,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
for (var cursor = self.entries(CursorOption.ANY_ORDER); cursor.advance(); ) {
|
||||
if (applyLambdaNode.executeBoolean(
|
||||
function, cursor.key(), readCursorValueNode.execute(frame, cursor))) {
|
||||
return true;
|
||||
}
|
||||
result.set(applyLambdaNode.executeBoolean(function, key, value));
|
||||
return !result.get();
|
||||
});
|
||||
return result.get();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class toMap extends ExternalMethod0Node {
|
||||
@Specialization
|
||||
protected VmMap eval(VmMapping self) {
|
||||
protected VmMap eval(
|
||||
VirtualFrame frame,
|
||||
VmMapping self,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
var builder = VmMap.builder();
|
||||
self.forceAndIterateMemberValues(
|
||||
(key, def, value) -> {
|
||||
builder.add(key, value);
|
||||
return true;
|
||||
});
|
||||
for (var cursor = self.entries(); cursor.advance(); ) {
|
||||
builder.add(cursor.key(), readCursorValueNode.execute(frame, cursor));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
package org.pkl.core.stdlib.base;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import org.pkl.core.runtime.*;
|
||||
import org.pkl.core.stdlib.AbstractStringRenderer;
|
||||
import org.pkl.core.stdlib.ExternalMethod1Node;
|
||||
@@ -29,9 +31,10 @@ public final class PListRendererNodes {
|
||||
public abstract static class renderDocument extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected String eval(VmTyped self, Object value) {
|
||||
protected String eval(
|
||||
VmTyped self, Object value, @Cached("create()") IndirectCallNode callNode) {
|
||||
var builder = new StringBuilder();
|
||||
createRenderer(self, builder).renderDocument(value);
|
||||
createRenderer(self, builder, callNode).renderDocument(value);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -39,16 +42,18 @@ public final class PListRendererNodes {
|
||||
public abstract static class renderValue extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected String eval(VmTyped self, Object value) {
|
||||
protected String eval(
|
||||
VmTyped self, Object value, @Cached("create()") IndirectCallNode callNode) {
|
||||
var builder = new StringBuilder();
|
||||
createRenderer(self, builder).renderValue(value);
|
||||
createRenderer(self, builder, callNode).renderValue(value);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static PListRenderer createRenderer(VmTyped self, StringBuilder builder) {
|
||||
private static PListRenderer createRenderer(
|
||||
VmTyped self, StringBuilder builder, IndirectCallNode callNode) {
|
||||
var indent = (String) VmUtils.readMember(self, Identifier.INDENT);
|
||||
return new PListRenderer(builder, indent, PklConverter.fromRenderer(self));
|
||||
return new PListRenderer(builder, indent, PklConverter.fromRenderer(self, callNode));
|
||||
}
|
||||
|
||||
// keep in sync with org.pkl.core.PListRenderer
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
package org.pkl.core.stdlib.base;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import org.pkl.core.runtime.*;
|
||||
import org.pkl.core.stdlib.ExternalMethod1Node;
|
||||
import org.pkl.core.stdlib.PklConverter;
|
||||
@@ -27,9 +29,10 @@ public final class PcfRendererNodes {
|
||||
public abstract static class renderDocument extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected String eval(VmTyped self, Object value) {
|
||||
protected String eval(
|
||||
VmTyped self, Object value, @Cached("create()") IndirectCallNode callNode) {
|
||||
var builder = new StringBuilder();
|
||||
createRenderer(self, builder).renderDocument(value);
|
||||
createRenderer(self, builder, callNode).renderDocument(value);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -37,22 +40,25 @@ public final class PcfRendererNodes {
|
||||
public abstract static class renderValue extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected String eval(VmTyped self, Object value) {
|
||||
protected String eval(
|
||||
VmTyped self, Object value, @Cached("create()") IndirectCallNode callNode) {
|
||||
var builder = new StringBuilder();
|
||||
createRenderer(self, builder).renderValue(value);
|
||||
createRenderer(self, builder, callNode).renderValue(value);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static PcfRenderer createRenderer(VmTyped self, StringBuilder builder) {
|
||||
var indent = (String) VmUtils.readMember(self, Identifier.INDENT);
|
||||
var omitNullProperties = (boolean) VmUtils.readMember(self, Identifier.OMIT_NULL_PROPERTIES);
|
||||
private static PcfRenderer createRenderer(
|
||||
VmTyped self, StringBuilder builder, IndirectCallNode callNode) {
|
||||
var indent = (String) VmUtils.readMember(self, Identifier.INDENT, callNode);
|
||||
var omitNullProperties =
|
||||
(boolean) VmUtils.readMember(self, Identifier.OMIT_NULL_PROPERTIES, callNode);
|
||||
var useCustomStringDelimiters =
|
||||
(boolean) VmUtils.readMember(self, Identifier.USE_CUSTOM_STRING_DELIMITERS);
|
||||
(boolean) VmUtils.readMember(self, Identifier.USE_CUSTOM_STRING_DELIMITERS, callNode);
|
||||
return new PcfRenderer(
|
||||
builder,
|
||||
indent,
|
||||
PklConverter.fromRenderer(self),
|
||||
PklConverter.fromRenderer(self, callNode),
|
||||
omitNullProperties,
|
||||
useCustomStringDelimiters);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
package org.pkl.core.stdlib.base;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import org.pkl.core.runtime.Identifier;
|
||||
import org.pkl.core.runtime.VmBytes;
|
||||
import org.pkl.core.runtime.VmDataSize;
|
||||
@@ -49,9 +51,10 @@ public final class PropertiesRendererNodes {
|
||||
public abstract static class renderDocument extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected String eval(VmTyped self, Object value) {
|
||||
protected String eval(
|
||||
VmTyped self, Object value, @Cached("create()") IndirectCallNode callNode) {
|
||||
var builder = new StringBuilder();
|
||||
createRenderer(self, builder).renderDocument(value);
|
||||
createRenderer(self, builder, callNode).renderDocument(value);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -59,18 +62,21 @@ public final class PropertiesRendererNodes {
|
||||
public abstract static class renderValue extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected String eval(VmTyped self, Object value) {
|
||||
protected String eval(
|
||||
VmTyped self, Object value, @Cached("create()") IndirectCallNode callNode) {
|
||||
var builder = new StringBuilder();
|
||||
createRenderer(self, builder).renderValue(value);
|
||||
createRenderer(self, builder, callNode).renderValue(value);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static PropertiesRenderer createRenderer(VmTyped self, StringBuilder builder) {
|
||||
var omitNullProperties = (boolean) VmUtils.readMember(self, Identifier.OMIT_NULL_PROPERTIES);
|
||||
var restrictCharset = (boolean) VmUtils.readMember(self, Identifier.RESTRICT_CHARSET);
|
||||
private static PropertiesRenderer createRenderer(
|
||||
VmTyped self, StringBuilder builder, IndirectCallNode callNode) {
|
||||
var omitNullProperties =
|
||||
(boolean) VmUtils.readMember(self, Identifier.OMIT_NULL_PROPERTIES, callNode);
|
||||
var restrictCharset = (boolean) VmUtils.readMember(self, Identifier.RESTRICT_CHARSET, callNode);
|
||||
return new PropertiesRenderer(
|
||||
builder, omitNullProperties, restrictCharset, PklConverter.fromRenderer(self));
|
||||
builder, omitNullProperties, restrictCharset, PklConverter.fromRenderer(self, callNode));
|
||||
}
|
||||
|
||||
private static final class PropertiesRenderer extends AbstractStringRenderer {
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.pkl.core.stdlib.base;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.LoopNode;
|
||||
@@ -27,6 +28,7 @@ import org.pkl.core.ast.expression.binary.LessThanNode;
|
||||
import org.pkl.core.ast.expression.binary.LessThanNodeGen;
|
||||
import org.pkl.core.ast.internal.IsInstanceOfNode;
|
||||
import org.pkl.core.ast.internal.IsInstanceOfNodeGen;
|
||||
import org.pkl.core.ast.internal.ReadCursorValueNode;
|
||||
import org.pkl.core.ast.internal.ToStringNode;
|
||||
import org.pkl.core.ast.internal.ToStringNodeGen;
|
||||
import org.pkl.core.ast.lambda.*;
|
||||
@@ -428,8 +430,11 @@ public final class SetNodes {
|
||||
|
||||
public abstract static class flatten extends ExternalMethod0Node {
|
||||
@Specialization
|
||||
protected VmSet eval(VmSet self) {
|
||||
return (VmSet) self.flatten();
|
||||
protected VmSet eval(
|
||||
VirtualFrame frame,
|
||||
VmSet self,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
return (VmSet) self.flatten(frame, readCursorValueNode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,9 +15,13 @@
|
||||
*/
|
||||
package org.pkl.core.stdlib.base;
|
||||
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import org.pkl.core.ast.internal.ReadCursorValueNode;
|
||||
import org.pkl.core.runtime.*;
|
||||
import org.pkl.core.runtime.VmObjectCursor.CursorOption;
|
||||
import org.pkl.core.stdlib.ExternalMethod0Node;
|
||||
import org.pkl.core.stdlib.ExternalMethod1Node;
|
||||
|
||||
@@ -51,15 +55,14 @@ public final class TypedNodes {
|
||||
|
||||
public abstract static class toMap extends ExternalMethod0Node {
|
||||
@Specialization
|
||||
protected VmMap eval(VmTyped self) {
|
||||
protected VmMap eval(
|
||||
VirtualFrame frame,
|
||||
VmTyped self,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode) {
|
||||
var builder = VmMap.builder();
|
||||
self.forceAndIterateMemberValues(
|
||||
(memberKey, memberDef, memberValue) -> {
|
||||
// exclude type definitions
|
||||
if (memberDef.isClass() || memberDef.isTypeAlias()) return true;
|
||||
builder.add(memberKey.toString(), memberValue);
|
||||
return true;
|
||||
});
|
||||
for (var cursor = self.properties(CursorOption.ALL_VALUES); cursor.advance(); ) {
|
||||
builder.add(cursor.keyToString(), readCursorValueNode.execute(frame, cursor));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
package org.pkl.core.stdlib.base;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import org.pkl.core.runtime.Identifier;
|
||||
import org.pkl.core.runtime.VmBytes;
|
||||
import org.pkl.core.runtime.VmCollection;
|
||||
@@ -30,6 +32,7 @@ import org.pkl.core.runtime.VmListing;
|
||||
import org.pkl.core.runtime.VmMap;
|
||||
import org.pkl.core.runtime.VmMapping;
|
||||
import org.pkl.core.runtime.VmNull;
|
||||
import org.pkl.core.runtime.VmObjectCursor.CursorOption;
|
||||
import org.pkl.core.runtime.VmPair;
|
||||
import org.pkl.core.runtime.VmRegex;
|
||||
import org.pkl.core.runtime.VmSet;
|
||||
@@ -38,7 +41,6 @@ import org.pkl.core.runtime.VmUtils;
|
||||
import org.pkl.core.stdlib.AbstractStringRenderer;
|
||||
import org.pkl.core.stdlib.ExternalMethod1Node;
|
||||
import org.pkl.core.stdlib.PklConverter;
|
||||
import org.pkl.core.util.MutableBoolean;
|
||||
import org.pkl.core.util.yaml.YamlEmitter;
|
||||
|
||||
public final class YamlRendererNodes {
|
||||
@@ -47,9 +49,10 @@ public final class YamlRendererNodes {
|
||||
public abstract static class renderDocument extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected String eval(VmTyped self, Object value) {
|
||||
protected String eval(
|
||||
VmTyped self, Object value, @Cached("create()") IndirectCallNode callNode) {
|
||||
var builder = new StringBuilder();
|
||||
createRenderer(self, builder).renderDocument(value);
|
||||
createRenderer(self, builder, callNode).renderDocument(value);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -57,29 +60,35 @@ public final class YamlRendererNodes {
|
||||
public abstract static class renderValue extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected String eval(VmTyped self, Object value) {
|
||||
protected String eval(
|
||||
VmTyped self, Object value, @Cached("create()") IndirectCallNode callNode) {
|
||||
var builder = new StringBuilder();
|
||||
createRenderer(self, builder).renderValue(value);
|
||||
createRenderer(self, builder, callNode).renderValue(value);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static YamlRenderer createRenderer(VmTyped self, StringBuilder builder) {
|
||||
var mode = ((String) VmUtils.readMember(self, Identifier.MODE));
|
||||
var indentWidth = ((Long) VmUtils.readMember(self, Identifier.INDENT_WIDTH)).intValue();
|
||||
var omitNullProperties = (boolean) VmUtils.readMember(self, Identifier.OMIT_NULL_PROPERTIES);
|
||||
var isStream = (boolean) VmUtils.readMember(self, Identifier.IS_STREAM);
|
||||
private static YamlRenderer createRenderer(
|
||||
VmTyped self, StringBuilder builder, IndirectCallNode callNode) {
|
||||
var mode = ((String) VmUtils.readMember(self, Identifier.MODE, callNode));
|
||||
var indentWidth =
|
||||
((Long) VmUtils.readMember(self, Identifier.INDENT_WIDTH, callNode)).intValue();
|
||||
var omitNullProperties =
|
||||
(boolean) VmUtils.readMember(self, Identifier.OMIT_NULL_PROPERTIES, callNode);
|
||||
var isStream = (boolean) VmUtils.readMember(self, Identifier.IS_STREAM, callNode);
|
||||
return new YamlRenderer(
|
||||
builder,
|
||||
" ".repeat(indentWidth),
|
||||
PklConverter.fromRenderer(self),
|
||||
PklConverter.fromRenderer(self, callNode),
|
||||
omitNullProperties,
|
||||
mode,
|
||||
isStream);
|
||||
isStream,
|
||||
callNode);
|
||||
}
|
||||
|
||||
private static final class YamlRenderer extends AbstractStringRenderer {
|
||||
private final boolean isStream;
|
||||
private final IndirectCallNode callNode;
|
||||
private final YamlEmitter emitter;
|
||||
private final String elementIndent;
|
||||
|
||||
@@ -89,10 +98,12 @@ public final class YamlRendererNodes {
|
||||
PklConverter converter,
|
||||
boolean omitNullProperties,
|
||||
String mode,
|
||||
boolean isStream) {
|
||||
boolean isStream,
|
||||
IndirectCallNode callNode) {
|
||||
super("YAML", builder, indent, converter, omitNullProperties, omitNullProperties);
|
||||
|
||||
this.isStream = isStream;
|
||||
this.callNode = callNode;
|
||||
this.emitter = YamlEmitter.create(builder, mode, indent);
|
||||
elementIndent = indent.substring(1);
|
||||
}
|
||||
@@ -109,16 +120,14 @@ public final class YamlRendererNodes {
|
||||
|
||||
private void visitStream(Object value) {
|
||||
if (value instanceof VmListing listing) {
|
||||
var isFirst = new MutableBoolean(true);
|
||||
listing.forceAndIterateMemberValues(
|
||||
((key, member, element) -> {
|
||||
if (!isFirst.getAndSetFalse()) {
|
||||
var cursor = listing.elements(CursorOption.ALL_VALUES);
|
||||
if (!cursor.advance()) return;
|
||||
visit(cursor.value(callNode));
|
||||
while (cursor.advance()) {
|
||||
startNewLine();
|
||||
builder.append("---");
|
||||
visit(cursor.value(callNode));
|
||||
}
|
||||
visit(element);
|
||||
return true;
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.pkl.core.stdlib.json;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Cached.Shared;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import java.util.*;
|
||||
@@ -39,20 +40,21 @@ public final class ParserNodes {
|
||||
public abstract static class parse extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected Object eval(VmTyped self, String text) {
|
||||
return doParse(self, text);
|
||||
protected Object eval(
|
||||
VmTyped self, String text, @Shared @Cached("create()") IndirectCallNode callNode) {
|
||||
return doParse(self, text, callNode);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected Object eval(
|
||||
VmTyped self, VmTyped resource, @Cached("create()") IndirectCallNode callNode) {
|
||||
VmTyped self, VmTyped resource, @Shared @Cached("create()") IndirectCallNode callNode) {
|
||||
var text = (String) VmUtils.readMember(resource, Identifier.TEXT, callNode);
|
||||
return doParse(self, text);
|
||||
return doParse(self, text, callNode);
|
||||
}
|
||||
|
||||
private Object doParse(VmTyped self, String text) {
|
||||
var converter = PklConverter.fromParser(self);
|
||||
private Object doParse(VmTyped self, String text, IndirectCallNode callNode) {
|
||||
var converter = PklConverter.fromParser(self, callNode);
|
||||
var useMapping = (boolean) VmUtils.readMember(self, Identifier.USE_MAPPING);
|
||||
var handler = new Handler(converter, useMapping);
|
||||
var parser = new JsonParser(handler);
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
package org.pkl.core.stdlib.jsonnet;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import org.pkl.core.runtime.Identifier;
|
||||
@@ -43,19 +45,22 @@ import org.pkl.core.util.ArrayCharEscaper;
|
||||
import org.pkl.core.util.IoUtils;
|
||||
|
||||
public final class RendererNodes {
|
||||
private static Renderer createRenderer(VmTyped self, StringBuilder builder) {
|
||||
private static Renderer createRenderer(
|
||||
VmTyped self, StringBuilder builder, IndirectCallNode callNode) {
|
||||
var indent = (String) VmNull.unwrap(VmUtils.readMember(self, Identifier.INDENT));
|
||||
if (indent == null) indent = "";
|
||||
var omitNullProperties = (boolean) VmUtils.readMember(self, Identifier.OMIT_NULL_PROPERTIES);
|
||||
return new Renderer(builder, indent, omitNullProperties, PklConverter.fromRenderer(self));
|
||||
return new Renderer(
|
||||
builder, indent, omitNullProperties, PklConverter.fromRenderer(self, callNode));
|
||||
}
|
||||
|
||||
public abstract static class renderDocument extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected String eval(VmTyped self, Object value) {
|
||||
protected String eval(
|
||||
VmTyped self, Object value, @Cached("create()") IndirectCallNode callNode) {
|
||||
var builder = new StringBuilder();
|
||||
createRenderer(self, builder).renderDocument(value);
|
||||
createRenderer(self, builder, callNode).renderDocument(value);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -63,9 +68,10 @@ public final class RendererNodes {
|
||||
public abstract static class renderValue extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected String eval(VmTyped self, Object value) {
|
||||
protected String eval(
|
||||
VmTyped self, Object value, @Cached("create()") IndirectCallNode callNode) {
|
||||
var builder = new StringBuilder();
|
||||
createRenderer(self, builder).renderValue(value);
|
||||
createRenderer(self, builder, callNode).renderValue(value);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,16 @@
|
||||
package org.pkl.core.stdlib.net;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.LoopNode;
|
||||
import com.oracle.truffle.api.profiles.LoopConditionProfile;
|
||||
import org.pkl.core.ast.internal.ReadCursorValueNode;
|
||||
import org.pkl.core.runtime.VmListing;
|
||||
import org.pkl.core.runtime.VmMapping;
|
||||
import org.pkl.core.runtime.VmTyped;
|
||||
import org.pkl.core.runtime.VmUtils;
|
||||
import org.pkl.core.stdlib.ExternalMethod1Node;
|
||||
import org.pkl.core.util.url.PercentEncoder;
|
||||
import org.pkl.core.util.url.UrlFactory;
|
||||
@@ -55,25 +61,37 @@ public final class NetNodes {
|
||||
|
||||
public abstract static class buildQuery extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected String eval(@SuppressWarnings("unused") VmTyped self, VmMapping parameters) {
|
||||
var out = new StringBuilder();
|
||||
parameters.forceAndIterateMemberValues(
|
||||
(name, def, values) -> {
|
||||
((VmListing) values)
|
||||
.forceAndIterateMemberValues(
|
||||
(index, valueDef, value) -> {
|
||||
if (!out.isEmpty()) {
|
||||
out.append('&');
|
||||
protected String eval(
|
||||
VirtualFrame frame,
|
||||
@SuppressWarnings("unused") VmTyped self,
|
||||
VmMapping parameters,
|
||||
@Cached("create()") ReadCursorValueNode readCursorValueNode,
|
||||
@Cached("create()") LoopConditionProfile outerLoopProfile,
|
||||
@Cached("create()") LoopConditionProfile innerLoopProfile) {
|
||||
var out = VmUtils.createBuilder();
|
||||
var count = 0;
|
||||
var isFirst = true;
|
||||
// don't use `inject()` for outer loop because mapping length can possibly transfer to
|
||||
// interpreter.
|
||||
for (var cursor = parameters.members(); outerLoopProfile.profile(cursor.advance()); ) {
|
||||
var values = (VmListing) readCursorValueNode.execute(frame, cursor);
|
||||
innerLoopProfile.profileCounted(values.getLength());
|
||||
for (var listingCursor = values.members();
|
||||
innerLoopProfile.inject(listingCursor.advance()); ) {
|
||||
count++;
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
} else {
|
||||
VmUtils.appendToBuilder(out, '&');
|
||||
}
|
||||
PercentEncoder.encodeForm(out, (String) name);
|
||||
out.append('=');
|
||||
PercentEncoder.encodeForm(out, (String) value);
|
||||
return true;
|
||||
});
|
||||
return true;
|
||||
});
|
||||
return out.toString();
|
||||
PercentEncoder.encodeForm(out, cursor.keyToString());
|
||||
VmUtils.appendToBuilder(out, '=');
|
||||
var value = (String) readCursorValueNode.execute(frame, listingCursor);
|
||||
PercentEncoder.encodeForm(out, value);
|
||||
}
|
||||
}
|
||||
LoopNode.reportLoopCount(this, count);
|
||||
return VmUtils.builderToString(out);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
package org.pkl.core.stdlib.pklbinary;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import org.msgpack.core.MessageBufferPacker;
|
||||
import org.msgpack.core.MessagePack;
|
||||
import org.pkl.core.runtime.VmBytes;
|
||||
@@ -30,9 +32,10 @@ public final class RendererNodes {
|
||||
public abstract static class renderDocument extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected VmBytes eval(VmTyped self, Object value) {
|
||||
protected VmBytes eval(
|
||||
VmTyped self, Object value, @Cached("create()") IndirectCallNode callNode) {
|
||||
var packer = MessagePack.newDefaultBufferPacker();
|
||||
createRenderer(self, packer).renderDocument(value);
|
||||
createRenderer(self, packer, callNode).renderDocument(value);
|
||||
return new VmBytes(packer.toByteArray());
|
||||
}
|
||||
}
|
||||
@@ -40,14 +43,16 @@ public final class RendererNodes {
|
||||
public abstract static class renderValue extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected VmBytes eval(VmTyped self, Object value) {
|
||||
protected VmBytes eval(
|
||||
VmTyped self, Object value, @Cached("create()") IndirectCallNode callNode) {
|
||||
var packer = MessagePack.newDefaultBufferPacker();
|
||||
createRenderer(self, packer).renderValue(value);
|
||||
createRenderer(self, packer, callNode).renderValue(value);
|
||||
return new VmBytes(packer.toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
private static VmPklBinaryEncoder createRenderer(VmTyped self, MessageBufferPacker packer) {
|
||||
return new VmPklBinaryEncoder(packer, PklConverter.fromRenderer(self));
|
||||
private static VmPklBinaryEncoder createRenderer(
|
||||
VmTyped self, MessageBufferPacker packer, IndirectCallNode callNode) {
|
||||
return new VmPklBinaryEncoder(packer, PklConverter.fromRenderer(self, callNode));
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,9 @@
|
||||
package org.pkl.core.stdlib.protobuf;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
@@ -90,9 +92,10 @@ public final class RendererNodes {
|
||||
public abstract static class renderDocument extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected String eval(VmTyped self, Object value) {
|
||||
protected String eval(
|
||||
VmTyped self, Object value, @Cached("create()") IndirectCallNode callNode) {
|
||||
var builder = new StringBuilder();
|
||||
createRenderer(self, builder).renderDocument(value);
|
||||
createRenderer(self, builder, callNode).renderDocument(value);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -100,9 +103,10 @@ public final class RendererNodes {
|
||||
public abstract static class renderValue extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected String eval(VmTyped self, Object value) {
|
||||
protected String eval(
|
||||
VmTyped self, Object value, @Cached("create()") IndirectCallNode callNode) {
|
||||
var builder = new StringBuilder();
|
||||
createRenderer(self, builder).renderValue(value);
|
||||
createRenderer(self, builder, callNode).renderValue(value);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -138,10 +142,10 @@ public final class RendererNodes {
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
private static ProtobufRenderer createRenderer(VmTyped self, StringBuilder builder) {
|
||||
var indent = (String) VmUtils.readMember(self, Identifier.INDENT);
|
||||
|
||||
return new ProtobufRenderer(builder, indent, PklConverter.fromRenderer(self));
|
||||
private static ProtobufRenderer createRenderer(
|
||||
VmTyped self, StringBuilder builder, IndirectCallNode callNode) {
|
||||
var indent = (String) VmUtils.readMember(self, Identifier.INDENT, callNode);
|
||||
return new ProtobufRenderer(builder, indent, PklConverter.fromRenderer(self, callNode));
|
||||
}
|
||||
|
||||
private static final class ProtobufRenderer extends AbstractStringRenderer {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.pkl.core.stdlib.test.report;
|
||||
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.ArrayList;
|
||||
@@ -208,7 +209,15 @@ public final class JUnitReporter implements TestReporter {
|
||||
|
||||
private static String renderXML(String indent, String version, VmDynamic value) {
|
||||
var builder = new StringBuilder();
|
||||
var renderer = new Renderer(builder, indent, version, "", VmMapping.empty(), PklConverter.NOOP);
|
||||
var renderer =
|
||||
new Renderer(
|
||||
builder,
|
||||
indent,
|
||||
version,
|
||||
"",
|
||||
VmMapping.empty(),
|
||||
PklConverter.NOOP,
|
||||
IndirectCallNode.getUncached());
|
||||
renderer.renderDocument(value);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@@ -16,9 +16,12 @@
|
||||
package org.pkl.core.stdlib.xml;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.pkl.core.runtime.*;
|
||||
import org.pkl.core.runtime.VmObjectCursor.CursorOption;
|
||||
import org.pkl.core.stdlib.AbstractStringRenderer;
|
||||
import org.pkl.core.stdlib.ExternalMethod1Node;
|
||||
import org.pkl.core.stdlib.PklConverter;
|
||||
@@ -28,7 +31,8 @@ import org.pkl.core.util.xml.XmlValidator;
|
||||
public final class RendererNodes {
|
||||
private RendererNodes() {}
|
||||
|
||||
private static Renderer createRenderer(VmTyped self, StringBuilder builder) {
|
||||
private static Renderer createRenderer(
|
||||
VmTyped self, StringBuilder builder, IndirectCallNode callNode) {
|
||||
var indent = (String) VmUtils.readMember(self, Identifier.INDENT);
|
||||
var xmlVersion = (String) VmUtils.readMember(self, Identifier.XML_VERSION);
|
||||
var rootElementName = (String) VmUtils.readMember(self, Identifier.ROOT_ELEMENT_NAME);
|
||||
@@ -40,15 +44,17 @@ public final class RendererNodes {
|
||||
xmlVersion,
|
||||
rootElementName,
|
||||
rootElementAttributes,
|
||||
PklConverter.fromRenderer(self));
|
||||
PklConverter.fromRenderer(self, callNode),
|
||||
callNode);
|
||||
}
|
||||
|
||||
public abstract static class renderDocument extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected String eval(VmTyped self, Object value) {
|
||||
protected String eval(
|
||||
VmTyped self, Object value, @Cached("create()") IndirectCallNode callNode) {
|
||||
var builder = new StringBuilder();
|
||||
createRenderer(self, builder).renderDocument(value);
|
||||
createRenderer(self, builder, callNode).renderDocument(value);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -56,9 +62,10 @@ public final class RendererNodes {
|
||||
public abstract static class renderValue extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected String eval(VmTyped self, Object value) {
|
||||
protected String eval(
|
||||
VmTyped self, Object value, @Cached("create()") IndirectCallNode callNode) {
|
||||
var builder = new StringBuilder();
|
||||
createRenderer(self, builder).renderValue(value);
|
||||
createRenderer(self, builder, callNode).renderValue(value);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -79,6 +86,7 @@ public final class RendererNodes {
|
||||
private final String rootElementName;
|
||||
private final VmMapping rootElementAttributes;
|
||||
private final XmlValidator validator;
|
||||
private final IndirectCallNode callNode;
|
||||
|
||||
private int lineNumber = 0;
|
||||
private @Nullable Object deferredKey;
|
||||
@@ -89,12 +97,14 @@ public final class RendererNodes {
|
||||
String version,
|
||||
String rootElementName,
|
||||
VmMapping rootElementAttributes,
|
||||
PklConverter converter) {
|
||||
PklConverter converter,
|
||||
IndirectCallNode callNode) {
|
||||
super("XML", builder, indent, converter, true, true);
|
||||
this.version = version;
|
||||
this.rootElementName = rootElementName;
|
||||
this.rootElementAttributes = rootElementAttributes;
|
||||
validator = XmlValidator.create(version);
|
||||
this.callNode = callNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -414,17 +424,15 @@ public final class RendererNodes {
|
||||
}
|
||||
builder.append("<").append(name);
|
||||
if (attributes != null) {
|
||||
attributes.forceAndIterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
for (var cursor = attributes.entries(); cursor.advance(); ) {
|
||||
builder.append(' ');
|
||||
// this check will be unnecessary once we have an XmlElement Pkl class
|
||||
if (!(key instanceof String string)) {
|
||||
throw new VmExceptionBuilder()
|
||||
.typeMismatch(name, BaseModule.getStringClass())
|
||||
.build();
|
||||
if (!(cursor.key() instanceof String string)) {
|
||||
throw new VmExceptionBuilder().typeMismatch(name, BaseModule.getStringClass()).build();
|
||||
}
|
||||
validateName(string, "attribute");
|
||||
builder.append(string).append("=\"");
|
||||
var value = cursor.value(callNode);
|
||||
// this check will be unnecessary once we have an XmlElement Pkl class
|
||||
if (!isScalar(value)) {
|
||||
throw new VmExceptionBuilder()
|
||||
@@ -433,8 +441,7 @@ public final class RendererNodes {
|
||||
.build();
|
||||
}
|
||||
builder.append(stringEscaper.escape(value.toString())).append("\"");
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
builder.append(">");
|
||||
|
||||
@@ -444,12 +451,10 @@ public final class RendererNodes {
|
||||
if (isXmlElement(content)) {
|
||||
// this special casing is necessary because renderXmlElement() is implemented in terms of
|
||||
// this method
|
||||
((VmDynamic) content)
|
||||
.forceAndIterateMemberValues(
|
||||
(key, member, value) -> {
|
||||
if (member.isElement()) doVisitElement(value);
|
||||
return true;
|
||||
});
|
||||
for (var cursor = ((VmDynamic) content).elements(CursorOption.ALL_VALUES);
|
||||
cursor.advance(); ) {
|
||||
doVisitElement(cursor.value(callNode));
|
||||
}
|
||||
} else {
|
||||
visit(content);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.pkl.core.stdlib.yaml;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Cached.Shared;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.IndirectCallNode;
|
||||
import com.oracle.truffle.api.source.Source;
|
||||
@@ -50,22 +51,23 @@ public final class ParserNodes {
|
||||
public abstract static class parse extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected Object eval(VmTyped self, String text) {
|
||||
protected Object eval(
|
||||
VmTyped self, String text, @Shared @Cached("create()") IndirectCallNode callNode) {
|
||||
var uri = "input_string";
|
||||
return doParse(self, text, uri);
|
||||
return doParse(self, text, uri, callNode);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected Object eval(
|
||||
VmTyped self, VmTyped resource, @Cached("create()") IndirectCallNode callNode) {
|
||||
VmTyped self, VmTyped resource, @Shared @Cached("create()") IndirectCallNode callNode) {
|
||||
var text = (String) VmUtils.readMember(resource, Identifier.TEXT, callNode);
|
||||
var uri = (String) VmUtils.readMember(resource, Identifier.URI, callNode);
|
||||
return doParse(self, text, uri);
|
||||
return doParse(self, text, uri, callNode);
|
||||
}
|
||||
|
||||
private Object doParse(VmTyped self, String text, String uri) {
|
||||
var converter = PklConverter.fromParser(self);
|
||||
private Object doParse(VmTyped self, String text, String uri, IndirectCallNode callNode) {
|
||||
var converter = PklConverter.fromParser(self, callNode);
|
||||
var load = createLoad(self, text, uri, converter);
|
||||
|
||||
try {
|
||||
@@ -86,22 +88,23 @@ public final class ParserNodes {
|
||||
public abstract static class parseAll extends ExternalMethod1Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected VmList eval(VmTyped self, String text) {
|
||||
protected VmList eval(
|
||||
VmTyped self, String text, @Shared @Cached("create()") IndirectCallNode callNode) {
|
||||
var uri = "input_string";
|
||||
return doParseAll(self, text, uri);
|
||||
return doParseAll(self, text, uri, callNode);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
protected Object eval(
|
||||
VmTyped self, VmTyped resource, @Cached("create()") IndirectCallNode callNode) {
|
||||
VmTyped self, VmTyped resource, @Shared @Cached("create()") IndirectCallNode callNode) {
|
||||
var text = (String) VmUtils.readMember(resource, Identifier.TEXT, callNode);
|
||||
var uri = (String) VmUtils.readMember(resource, Identifier.URI, callNode);
|
||||
return doParseAll(self, text, uri);
|
||||
return doParseAll(self, text, uri, callNode);
|
||||
}
|
||||
|
||||
private VmList doParseAll(VmTyped self, String text, String uri) {
|
||||
var converter = PklConverter.fromParser(self);
|
||||
private VmList doParseAll(VmTyped self, String text, String uri, IndirectCallNode callNode) {
|
||||
var converter = PklConverter.fromParser(self, callNode);
|
||||
var load = createLoad(self, text, uri, converter);
|
||||
var builder = VmList.EMPTY.builder();
|
||||
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.core.util;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A builder for typed arrays that is convenient, efficient, and safe for Truffle partial
|
||||
* evaluation.
|
||||
*/
|
||||
public final class ArrayBuilder<T> {
|
||||
static final int INITIAL_LENGTH = 8;
|
||||
|
||||
private final ArrayFactory<T> factory;
|
||||
private T @Nullable [] array;
|
||||
private int nextIndex;
|
||||
|
||||
/**
|
||||
* Returns a new builder that creates arrays with the specified factory. The first time an element
|
||||
* is added, an array of length 8 is created. Whenever the array becomes too small, it is grown to
|
||||
* twice its previous length.
|
||||
*/
|
||||
public static <T extends @Nullable Object> ArrayBuilder<T> of(ArrayFactory<T> factory) {
|
||||
return new ArrayBuilder<>(factory);
|
||||
}
|
||||
|
||||
private ArrayBuilder(ArrayFactory<T> factory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
/** Adds {@code value} to the array. */
|
||||
public void add(T value) {
|
||||
if (array == null) {
|
||||
array = factory.apply(INITIAL_LENGTH);
|
||||
} else if (nextIndex >= array.length) {
|
||||
var newArray = factory.apply(Math.multiplyExact(array.length, 2));
|
||||
System.arraycopy(array, 0, newArray, 0, array.length);
|
||||
array = newArray;
|
||||
}
|
||||
array[nextIndex] = value;
|
||||
nextIndex += 1;
|
||||
}
|
||||
|
||||
/** Returns the current array, which may be larger than the number of elements added. */
|
||||
@SuppressWarnings("unchecked")
|
||||
public <R extends T> R[] toOversizedArray() {
|
||||
return array == null ? (R[]) factory.apply(0) : (R[]) array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array that exactly fits the number of elements added, potentially requiring a copy.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <R extends T> R[] toArray() {
|
||||
if (array == null) {
|
||||
return (R[]) factory.apply(0);
|
||||
}
|
||||
if (array.length == nextIndex) {
|
||||
return (R[]) array;
|
||||
}
|
||||
var newArray = factory.apply(nextIndex);
|
||||
System.arraycopy(array, 0, newArray, 0, nextIndex);
|
||||
return (R[]) newArray;
|
||||
}
|
||||
|
||||
/** Tells whether no elements have been added to the array. */
|
||||
public boolean isEmpty() {
|
||||
return nextIndex == 0;
|
||||
}
|
||||
|
||||
/** The number of elements added to the array. */
|
||||
public int length() {
|
||||
return nextIndex;
|
||||
}
|
||||
|
||||
/** The index of the last element added to the array. */
|
||||
public int lastIndex() {
|
||||
return nextIndex - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use own type instead of {@link java.util.function.IntFunction} because SnakeYAML uses it too.
|
||||
* Otherwise, native-iamge's analyzer will think that SnakeYAML's code is also reachable for
|
||||
* partial evaluation.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ArrayFactory<T> {
|
||||
T[] apply(int value);
|
||||
}
|
||||
}
|
||||
@@ -60,4 +60,14 @@ public final class CollectionUtils {
|
||||
// this is exactly how java.util.HashMap does it
|
||||
return (int) Math.ceil(expectedSize / (double) LOAD_FACTOR);
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
public static <K, V> @Nullable V put(Map<K, V> map, K key, V value) {
|
||||
return map.put(key, value);
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
public static <E> boolean contains(Collection<E> collection, E value) {
|
||||
return collection.contains(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
local m1 = new Mapping {
|
||||
["a"] = 1
|
||||
["b"] = 2
|
||||
}
|
||||
|
||||
m2 = (m1) {
|
||||
["any"] = m2.any((_, v) -> v == 2)
|
||||
["containsValue"] = m2.containsValue(2)
|
||||
}
|
||||
|
||||
m3 = (m1) {
|
||||
["isEmpty"] = m3.isEmpty
|
||||
}
|
||||
|
||||
local l1 = new Listing { 1; 2 }
|
||||
|
||||
l2 = (l1) {
|
||||
l2.contains(2)
|
||||
l2.any((it) -> it == 1)
|
||||
}
|
||||
Vendored
+4
@@ -10,6 +10,10 @@ x | undefinedValue
|
||||
^^^^^^^^^^^^^^
|
||||
at traceThenIterateUndefinedMember#items[#1] (file:///$snippetsDir/input/errors/traceThenIterateUndefinedMember.pkl)
|
||||
|
||||
xx | for (item in items) {
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
at traceThenIterateUndefinedMember#result (file:///$snippetsDir/input/errors/traceThenIterateUndefinedMember.pkl)
|
||||
|
||||
xx | let (_ = trace(items))
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
at traceThenIterateUndefinedMember#result.<let expr> (file:///$snippetsDir/input/errors/traceThenIterateUndefinedMember.pkl)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
m2 {
|
||||
["a"] = 1
|
||||
["b"] = 2
|
||||
["any"] = true
|
||||
["containsValue"] = true
|
||||
}
|
||||
m3 {
|
||||
["a"] = 1
|
||||
["b"] = 2
|
||||
["isEmpty"] = false
|
||||
}
|
||||
l2 {
|
||||
1
|
||||
2
|
||||
true
|
||||
true
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.core.util
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
// ArrayBuilder is designed for Java; it is less nice to use from Kotlin
|
||||
class ArrayBuilderTest {
|
||||
private data class Person(val name: String)
|
||||
|
||||
private val builder = ArrayBuilder.of<Person?> { arrayOfNulls<Person>(it) }
|
||||
private val alice = Person("Alice")
|
||||
|
||||
@Test
|
||||
fun `add zero elements`() {
|
||||
val elementCount = 0
|
||||
assertThat(builder.isEmpty).isTrue()
|
||||
assertThat(builder.length()).isEqualTo(elementCount)
|
||||
assertThat(builder.lastIndex()).isEqualTo(elementCount - 1)
|
||||
|
||||
val array = builder.toOversizedArray<Person>()
|
||||
assertThat(array.javaClass.componentType).isEqualTo(Person::class.java)
|
||||
assertThat(array).isEmpty()
|
||||
|
||||
val array2 = builder.toArray<Person>()
|
||||
assertThat(array2.javaClass.componentType).isEqualTo(Person::class.java)
|
||||
assertThat(array2).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `add fewer elements than initial array length`() {
|
||||
val elementCount = ArrayBuilder.INITIAL_LENGTH - 1
|
||||
repeat(elementCount) { builder.add(alice) }
|
||||
|
||||
assertThat(builder.isEmpty).isFalse()
|
||||
assertThat(builder.length()).isEqualTo(elementCount)
|
||||
assertThat(builder.lastIndex()).isEqualTo(elementCount - 1)
|
||||
|
||||
val array = builder.toOversizedArray<Person>()
|
||||
assertThat(array.javaClass.componentType).isEqualTo(Person::class.java)
|
||||
assertThat(array).hasSize(ArrayBuilder.INITIAL_LENGTH)
|
||||
for (i in 0 until elementCount) {
|
||||
assertThat(array[i]).isSameAs(alice)
|
||||
}
|
||||
for (i in elementCount..array.lastIndex) {
|
||||
assertThat(array[i]).isNull()
|
||||
}
|
||||
|
||||
val array2 = builder.toArray<Person>()
|
||||
assertThat(array2.javaClass.componentType).isEqualTo(Person::class.java)
|
||||
assertThat(array2).hasSize(elementCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `add same number of elements as initial array length`() {
|
||||
val elementCount = ArrayBuilder.INITIAL_LENGTH
|
||||
repeat(elementCount) { builder.add(alice) }
|
||||
|
||||
assertThat(builder.isEmpty).isFalse()
|
||||
assertThat(builder.length()).isEqualTo(elementCount)
|
||||
assertThat(builder.lastIndex()).isEqualTo(elementCount - 1)
|
||||
|
||||
val array = builder.toOversizedArray<Person>()
|
||||
assertThat(array.javaClass.componentType).isEqualTo(Person::class.java)
|
||||
assertThat(array).hasSize(elementCount)
|
||||
array.forEach { assertThat(it).isSameAs(alice) }
|
||||
assertThat(builder.toArray<Person>()).isSameAs(array)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `add more elements than initial array length`() {
|
||||
val elementCount = ArrayBuilder.INITIAL_LENGTH + 1
|
||||
repeat(elementCount) { builder.add(alice) }
|
||||
|
||||
assertThat(builder.isEmpty).isFalse()
|
||||
assertThat(builder.length()).isEqualTo(elementCount)
|
||||
assertThat(builder.lastIndex()).isEqualTo(elementCount - 1)
|
||||
|
||||
val array = builder.toOversizedArray<Person>()
|
||||
assertThat(array.javaClass.componentType).isEqualTo(Person::class.java)
|
||||
assertThat(array).hasSize(ArrayBuilder.INITIAL_LENGTH * 2)
|
||||
for (i in 0 until elementCount) {
|
||||
assertThat(array[i]).isSameAs(alice)
|
||||
}
|
||||
for (i in elementCount..array.lastIndex) {
|
||||
assertThat(array[i]).isNull()
|
||||
}
|
||||
|
||||
val array2 = builder.toArray<Person>()
|
||||
assertThat(array2.javaClass.componentType).isEqualTo(Person::class.java)
|
||||
assertThat(array2).hasSize(elementCount)
|
||||
array2.forEach { assertThat(it).isSameAs(alice) }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user