diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/base/ListingNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/base/ListingNodes.java index 72d07916..30488b94 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/base/ListingNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/base/ListingNodes.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -18,6 +18,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.Specialization; +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.lambda.*; @@ -62,6 +63,20 @@ public final class ListingNodes { } } + public abstract static class getOrDefault extends ExternalMethod1Node { + @Child private IndirectCallNode callNode = IndirectCallNode.create(); + @Child private ApplyVmFunction1Node applyNode = ApplyVmFunction1Node.create(); + + @Specialization + protected Object eval(VmListing self, long index) { + if (index < 0 || index >= self.getLength()) { + var defaultFunction = (VmFunction) VmUtils.readMember(self, Identifier.DEFAULT, callNode); + return applyNode.execute(defaultFunction, index); + } + return VmUtils.readMember(self, index); + } + } + public abstract static class isDistinct extends ExternalPropertyNode { @Specialization @TruffleBoundary diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/base/MappingNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/base/MappingNodes.java index ea00cbcc..56004ef8 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/base/MappingNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/base/MappingNodes.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 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. @@ -19,6 +19,7 @@ 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.ApplyVmFunction1Node; import org.pkl.core.ast.lambda.ApplyVmFunction2Node; import org.pkl.core.ast.lambda.ApplyVmFunction2NodeGen; import org.pkl.core.ast.lambda.ApplyVmFunction3Node; @@ -114,6 +115,22 @@ public final class MappingNodes { } } + public abstract static class getOrDefault extends ExternalMethod1Node { + @Child private IndirectCallNode callNode = IndirectCallNode.create(); + @Child private ApplyVmFunction1Node applyNode = ApplyVmFunction1Node.create(); + + @Specialization + protected Object eval(VmMapping self, Object key) { + var value = VmUtils.readMemberOrNull(self, key, callNode); + if (value != null) { + return value; + } + + var defaultFunction = (VmFunction) VmUtils.readMember(self, Identifier.DEFAULT, callNode); + return applyNode.execute(defaultFunction, key); + } + } + public abstract static class fold extends ExternalMethod2Node { @Child private ApplyVmFunction3Node applyLambdaNode = ApplyVmFunction3NodeGen.create(); diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/listings/default.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/listings/default.pkl index 4788e305..84ddf8c7 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/listings/default.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/listings/default.pkl @@ -29,3 +29,7 @@ res4 = (res3) { name = "Barn Owl" } } + +res5 = (res4.getOrDefault(99)) { + name = "Bald Eagle" +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/mappings/default.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/mappings/default.pkl index 72de98b3..87be530b 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/mappings/default.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/mappings/default.pkl @@ -38,3 +38,7 @@ res3: Mapping = new { age = 60 } } + +res4 = (res3.getOrDefault("Bald Eagle")) { + age = 99 +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/listings/default.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/listings/default.pcf index 1901294e..603559f4 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/listings/default.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/listings/default.pcf @@ -38,3 +38,7 @@ res4 { age = 5 } } +res5 { + name = "Bald Eagle" + age = 99 +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/mappings/default.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/mappings/default.pcf index 541dbbed..31d481e8 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/mappings/default.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/mappings/default.pcf @@ -32,3 +32,7 @@ res3 { age = 60 } } +res4 { + name = "Bald Eagle" + age = 99 +} diff --git a/stdlib/base.pkl b/stdlib/base.pkl index f5d00101..d0107bc8 100644 --- a/stdlib/base.pkl +++ b/stdlib/base.pkl @@ -1887,6 +1887,14 @@ class Listing extends Object { @Since { version = "0.27.0" } external function getOrNull(index: Int): Element? + /// Returns the element at [index]. + /// + /// Returns [default] applied to [index] if [index] is outside the bounds of this listing. + /// + /// This is equivalent to `getOrNull(index) ?? default.apply(index)`. + @Since { version = "0.29.0" } + external function getOrDefault(index: Int): Element + /// Tells if this listing has no duplicate elements. /// /// Facts: @@ -2050,6 +2058,13 @@ class Mapping extends Object { /// This is the nullable equivalent of the subscript operator (`object[key]`). external function getOrNull(key: Any): Value? + /// Returns the value associated with [key] or [default] applied to [key] if this mapping does + /// not contain [key]. + /// + /// This is equivalent to `getOrNull(key) ?? default.apply(key)`. + @Since { version = "0.29.0" } + external function getOrDefault(key: Any): Value + /// Folds the entries of this mapping in iteration order using [operator], starting with [initial]. external function fold(initial: Result, operator: (Result, Key, Value) -> Result): Result