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

* Add `values` to `Mapping`

* Add `entries` to `Mapping`

* Add `containsValue` to `Mapping`

* Add `every` to `Mapping`

* Add `any` to `Mapping`

* Add `toDynamic` to `Mapping`

* Add `lastIndex` to `Listing`

* Add `getOrNull` to `Listing`

* Add `first` to `Listing`

* Add `firstOrNull` to `Listing`

* Add `last` to `Listing`

* Add `lastOrNull` to `Listing`

* Add `single` to `Listing`

* Add `singleOrNull` to `Listing`

* Add `contains` to `Listing`

* Add `any` to `Listing`

* Add `every` to `Listing`

* Fixup `any` to `Listing`

* Revert "Add `toDynamic` to `Mapping`"

This reverts commit 5551974ecd8110aa2eb8f546e992c32d3181df9b.

* Revert "Add `values` to `Mapping`"

This reverts commit 6fc78796

* Revert "Add `entries` to `Mapping`"

This reverts commit a7e8dfc4

* Annotate new members with `Since` 0.27

* Fix documentation in `base.pkl`

* Add location information to empty/single checks in `Listing` operations

* Remove additional variable for laziness preservation

* Apply spotless

* Apply suggestions from code review

Co-authored-by: Daniel Chao <daniel.h.chao@gmail.com>

---------

Co-authored-by: Daniel Chao <daniel.h.chao@gmail.com>
This commit is contained in:
Philip K.F. Hölzenspies
2024-10-31 14:54:27 +00:00
committed by GitHub
parent 71db4d0fae
commit a03827951c
8 changed files with 503 additions and 11 deletions

View File

@@ -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.*;
@@ -44,6 +46,23 @@ public final class ListingNodes {
}
}
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
@@ -89,6 +108,58 @@ public final class ListingNodes {
}
}
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();
@@ -116,6 +187,59 @@ public final class ListingNodes {
}
}
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();
});
LoopNode.reportLoopCount(this, self.getLength());
return result.get();
}
}
public abstract static class fold extends ExternalMethod2Node {
@Child private ApplyVmFunction2Node applyLambdaNode = ApplyVmFunction2NodeGen.create();
@@ -194,4 +318,24 @@ public final class ListingNodes {
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();
}
}
}

View File

@@ -19,6 +19,8 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
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.*;
@@ -27,6 +29,7 @@ import org.pkl.core.stdlib.ExternalMethod1Node;
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;
@@ -86,6 +89,22 @@ 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);
}
foundValue.set(value.equals(memberValue));
return !foundValue.get();
});
return foundValue.get();
}
}
public abstract static class getOrNull extends ExternalMethod1Node {
@Child private IndirectCallNode callNode = IndirectCallNode.create();
@@ -110,6 +129,42 @@ public final class MappingNodes {
}
}
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) {

View File

@@ -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.

View File

@@ -23,6 +23,10 @@ local duplicate: Listing<Person> = (base) {
new { name = "Elf Owl" }
}
local altered: Listing<Person> = (base) {
[0] { name = "Wood Pigeon" }
}
facts {
["isEmpty"] {
empty.isEmpty
@@ -30,6 +34,14 @@ facts {
!base.isEmpty
!derived.isEmpty
}
["lastIndex"] {
empty.lastIndex == -1
empty2.lastIndex == -1
base.lastIndex == 2
derived.lastIndex == 4
duplicate.lastIndex == 5
}
["isDistinct"] {
empty.isDistinct
@@ -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]
}
["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 {

View File

@@ -57,19 +57,26 @@ facts {
!empty.containsKey("default")
!empty2.containsKey("Pigeon")
}
["containsValue()"] {
!empty.containsValue("Any value")
for (_, v in derived) {
derived.containsValue(v)
}
}
["length"] {
empty.length == 0
base.length == 2
derived.length == 3
}
["keys (of type string)"] {
empty.keys == Set()
derived.keys == Set("Pigeon", "Parrot", "Barn Owl")
base.keys == Set("Pigeon", "Parrot")
}
["keys (of type object)"] {
local base2 = new Mapping {
[empty] = "one"
@@ -82,6 +89,18 @@ facts {
base2.keys == Set(empty, base)
derived2.keys == Set(empty, base, derived)
}
["every()"] {
empty.every((_, _) -> throw("unreachable code"))
base.every((k, v) -> k == "Parrot" || v.age > 30)
!base.every((k, _) -> k == "Pigeon")
}
["any()"] {
base.any((k, _) -> k.contains("rot"))
base.any((_, v) -> v.age > 40)
!base.any((k, _) -> k.contains("other"))
}
}
examples {

View File

@@ -5,6 +5,13 @@ facts {
true
true
}
["lastIndex"] {
true
true
true
true
true
}
["isDistinct"] {
true
true
@@ -29,6 +36,62 @@ facts {
true
true
}
["getOrNull"] {
true
true
true
true
true
true
true
}
["first"] {
true
true
true
}
["firstOrNull"] {
true
true
true
}
["last"] {
true
true
true
}
["lastOrNull"] {
true
true
true
}
["single"] {
true
true
true
}
["singleOrNull"] {
true
true
true
}
["every"] {
true
true
true
}
["any"] {
true
true
true
}
["contains"] {
true
true
true
true
true
}
}
examples {
["length"] {

View File

@@ -18,6 +18,12 @@ facts {
true
true
}
["containsValue()"] {
true
true
true
true
}
["length"] {
true
true
@@ -32,6 +38,16 @@ facts {
true
true
}
["every()"] {
true
true
true
}
["any()"] {
true
true
true
}
}
examples {
["getOrNull()"] {