Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add methods from List/Map to Listing/Mapping #683

Merged
merged 27 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e5c927f
Add `values` to `Mapping`
holzensp Oct 7, 2024
66a8d38
Add `entries` to `Mapping`
holzensp Oct 7, 2024
d515cc7
Add `containsValue` to `Mapping`
holzensp Oct 7, 2024
541659d
Add `every` to `Mapping`
holzensp Oct 7, 2024
239ea98
Add `any` to `Mapping`
holzensp Oct 7, 2024
90a68a5
Add `toDynamic` to `Mapping`
holzensp Oct 8, 2024
a069d05
Add `lastIndex` to `Listing`
holzensp Oct 9, 2024
ca8f0ed
Add `getOrNull` to `Listing`
holzensp Oct 9, 2024
aa0f67f
Add `first` to `Listing`
holzensp Oct 9, 2024
b90699a
Add `firstOrNull` to `Listing`
holzensp Oct 9, 2024
065131c
Add `last` to `Listing`
holzensp Oct 9, 2024
a8fd1ff
Add `lastOrNull` to `Listing`
holzensp Oct 9, 2024
d0e8d72
Add `single` to `Listing`
holzensp Oct 9, 2024
2cadc33
Add `singleOrNull` to `Listing`
holzensp Oct 9, 2024
0ffc53c
Add `contains` to `Listing`
holzensp Oct 9, 2024
94f8d5e
Add `any` to `Listing`
holzensp Oct 25, 2024
02c0d62
Add `every` to `Listing`
holzensp Oct 25, 2024
7b9ea74
Fixup `any` to `Listing`
holzensp Oct 25, 2024
bc4cc80
Revert "Add `toDynamic` to `Mapping`"
holzensp Oct 30, 2024
565c568
Revert "Add `values` to `Mapping`"
holzensp Oct 30, 2024
0d00755
Revert "Add `entries` to `Mapping`"
holzensp Oct 30, 2024
c89b2a4
Annotate new members with `Since` 0.27
holzensp Oct 30, 2024
1b24877
Fix documentation in `base.pkl`
holzensp Oct 30, 2024
e480a4d
Add location information to empty/single checks in `Listing` operations
holzensp Oct 30, 2024
1351f0a
Remove additional variable for laziness preservation
holzensp Oct 30, 2024
1d2d51b
Apply spotless
holzensp Oct 30, 2024
adb3bb7
Apply suggestions from code review
holzensp Oct 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions pkl-core/src/main/java/org/pkl/core/stdlib/base/ListingNodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
*/
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.Specialization;
import com.oracle.truffle.api.nodes.LoopNode;
import org.pkl.core.ast.PklNode;
import org.pkl.core.ast.lambda.*;
import org.pkl.core.ast.member.ObjectMember;
import org.pkl.core.runtime.*;
Expand All @@ -44,6 +46,23 @@ protected boolean eval(VmListing self) {
}
}

public abstract static class lastIndex extends ExternalPropertyNode {
@Specialization
protected long eval(VmListing self) {
return self.getLength() - 1;
}
}

public abstract static class getOrNull extends ExternalMethod1Node {
@Specialization
protected Object eval(VmListing self, long index) {
if (index < 0 || index >= self.getLength()) {
return VmNull.withoutDefault();
}
return VmUtils.readMember(self, index);
}
}

public abstract static class isDistinct extends ExternalPropertyNode {
@Specialization
@TruffleBoundary
Expand Down Expand Up @@ -89,6 +108,58 @@ protected VmListing eval(VmListing self) {
}
}

public abstract static class first extends ExternalPropertyNode {
@Specialization
protected Object eval(VmListing self) {
checkNonEmpty(self, this);
return VmUtils.readMember(self, 0L);
}
}

public abstract static class firstOrNull extends ExternalPropertyNode {
@Specialization
protected Object eval(VmListing self) {
if (self.isEmpty()) {
return VmNull.withoutDefault();
}
return VmUtils.readMember(self, 0L);
}
}

public abstract static class last extends ExternalPropertyNode {
@Specialization
protected Object eval(VmListing self) {
checkNonEmpty(self, this);
return VmUtils.readMember(self, self.getLength() - 1L);
}
}

public abstract static class lastOrNull extends ExternalPropertyNode {
@Specialization
protected Object eval(VmListing self) {
var length = self.getLength();
return length == 0 ? VmNull.withoutDefault() : VmUtils.readMember(self, length - 1L);
}
}

public abstract static class single extends ExternalPropertyNode {
@Specialization
protected Object eval(VmListing self) {
checkSingleton(self, this);
return VmUtils.readMember(self, 0L);
}
}

public abstract static class singleOrNull extends ExternalPropertyNode {
@Specialization
protected Object eval(VmListing self) {
if (self.getLength() != 1) {
return VmNull.withoutDefault();
}
return VmUtils.readMember(self, 0L);
}
}

public abstract static class distinctBy extends ExternalMethod1Node {
@Child private ApplyVmFunction1Node applyNode = ApplyVmFunction1Node.create();

Expand Down Expand Up @@ -116,6 +187,59 @@ protected VmListing eval(VmListing self, VmFunction selector) {
}
}

public abstract static class every extends ExternalMethod1Node {
@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);
}
result.set(applyNode.executeBoolean(predicate, value));
return result.get();
});
return result.get();
}
}

public abstract static class any extends ExternalMethod1Node {
@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);
}
result.set(applyNode.executeBoolean(predicate, value));
return !result.get();
});
return result.get();
}
}

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);
}
result.set(element.equals(value));
return !result.get();
});
holzensp marked this conversation as resolved.
Show resolved Hide resolved
LoopNode.reportLoopCount(this, self.getLength());
return result.get();
}
}

public abstract static class fold extends ExternalMethod2Node {
@Child private ApplyVmFunction2Node applyLambdaNode = ApplyVmFunction2NodeGen.create();

Expand Down Expand Up @@ -194,4 +318,24 @@ protected VmSet eval(VmListing self) {
return builder.build();
}
}

private static void checkNonEmpty(VmListing self, PklNode node) {
if (self.isEmpty()) {
CompilerDirectives.transferToInterpreter();
throw new VmExceptionBuilder()
.evalError("expectedNonEmptyListing")
.withLocation(node)
.build();
}
}

private static void checkSingleton(VmListing self, PklNode node) {
if (self.getLength() != 1) {
CompilerDirectives.transferToInterpreter();
throw new VmExceptionBuilder()
.evalError("expectedSingleElementListing")
.withLocation(node)
.build();
}
}
holzensp marked this conversation as resolved.
Show resolved Hide resolved
}
55 changes: 55 additions & 0 deletions pkl-core/src/main/java/org/pkl/core/stdlib/base/MappingNodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.IndirectCallNode;
import java.util.HashSet;
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.*;
Expand All @@ -27,6 +29,7 @@
import org.pkl.core.stdlib.ExternalMethod2Node;
import org.pkl.core.stdlib.ExternalPropertyNode;
import org.pkl.core.util.EconomicMaps;
import org.pkl.core.util.MutableBoolean;
import org.pkl.core.util.MutableLong;
import org.pkl.core.util.MutableReference;

Expand Down Expand Up @@ -86,6 +89,22 @@ protected boolean eval(VmMapping self, Object key) {
}
}

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);
}
foundValue.set(value.equals(memberValue));
return !foundValue.get();
});
return foundValue.get();
}
}

public abstract static class getOrNull extends ExternalMethod1Node {
@Child private IndirectCallNode callNode = IndirectCallNode.create();

Expand All @@ -110,6 +129,42 @@ protected Object eval(VmMapping self, Object initial, VmFunction function) {
}
}

public abstract static class every extends ExternalMethod1Node {
@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);
}
result.set(applyLambdaNode.executeBoolean(function, key, value));
return result.get();
});
return result.get();
}
}

public abstract static class any extends ExternalMethod1Node {
@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);
}
result.set(applyLambdaNode.executeBoolean(function, key, value));
return !result.get();
});
return result.get();
}
}

public abstract static class toMap extends ExternalMethod0Node {
@Specialization
protected VmMap eval(VmMapping self) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,12 @@ Expected a single-element collection.
cannotFlattenCollectionWithNonCollectionElement=\
Cannot flatten a collection containing a non-collection element.

expectedNonEmptyListing=\
Expected a non-empty Listing.

expectedSingleElementListing=\
Expected a single-element Listing.

integerOverflow=\
Integer overflow.

Expand Down
78 changes: 78 additions & 0 deletions pkl-core/src/test/files/LanguageSnippetTests/input/api/listing.pkl
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,25 @@ local duplicate: Listing<Person> = (base) {
new { name = "Elf Owl" }
}

local altered: Listing<Person> = (base) {
[0] { name = "Wood Pigeon" }
}

facts {
["isEmpty"] {
empty.isEmpty
empty2.isEmpty
!base.isEmpty
!derived.isEmpty
}

["lastIndex"] {
empty.lastIndex == -1
empty2.lastIndex == -1
base.lastIndex == 2
derived.lastIndex == 4
duplicate.lastIndex == 5
}

["isDistinct"] {
empty.isDistinct
Expand Down Expand Up @@ -58,6 +70,72 @@ facts {
!derived.isDistinctBy((it) -> it.getClass())
!duplicate.isDistinctBy((it) -> it.getClass())
}

["getOrNull"] {
empty.getOrNull(-1) == null
empty.getOrNull(0) == null
base.getOrNull(-1) == null
for (i, v in base) {
base.getOrNull(i) == v
}
base.getOrNull(base.length) == null
}

["first"] {
module.catch(() -> empty.first) == "Expected a non-empty Listing."
base.first == base[0]
derived.first == base[0]
}

["firstOrNull"] {
empty.firstOrNull == null
base.firstOrNull == base[0]
derived.firstOrNull == base[0]
holzensp marked this conversation as resolved.
Show resolved Hide resolved
}

["last"] {
module.catch(() -> empty.last) == "Expected a non-empty Listing."
base.last == base[2]
derived.last == derived[4]
}

["lastOrNull"] {
empty.lastOrNull == null
base.lastOrNull == base[2]
derived.lastOrNull == derived[4]
}

["single"] {
module.catch(() -> empty.single) == "Expected a single-element Listing."
module.catch(() -> base.single) == "Expected a single-element Listing."
new Listing { 42 }.single == 42
}

["singleOrNull"] {
empty.singleOrNull == null
base.singleOrNull == null
new Listing { 42 }.singleOrNull == 42
}

["every"] {
!base.every((it) -> it.name.contains("rot"))
base.every((it) -> !it.name.isBlank)
!((base) { new { name = "EEEEE" } }).every((it) -> it.name.contains("rot"))
}

["any"] {
base.any((it) -> it.name.contains("rot"))
!base.any((it) -> it.name.contains("inch"))
((base) { new { name = "EEEEE" } }).any((it) -> it.name.contains("rot"))
}

["contains"] {
!empty.contains(0)
base.contains(base[1])
derived.contains(base[1])
derived.contains(derived[3])
!altered.contains(base[0])
}
}

examples {
Expand Down
Loading