Implement Pkl binary renderer and parser (#1203)

Implements a binary renderer for Pkl values, which is a lossless capturing of Pkl data.

This follows the pkl binary format that is already used with `pkl server` calls, and is
made available as a Java API and also an in-language API.

Also, introduces a binary parser into the corresponding `PObject` types in Java.
This commit is contained in:
Jen Basch
2025-10-20 09:10:22 -07:00
committed by GitHub
parent c602dbb84c
commit 6c036bf82a
298 changed files with 4236 additions and 2581 deletions

View File

@@ -36,7 +36,9 @@ For example, value `8` gets encoded as MessagePack `int8` format.
== Non-primitives
All non-primitive values are encoded as MessagePack arrays.
The first slot of the array designates the value's type. The remaining slots have fixed meanings depending on the type.
The first slot of the array designates the value's type.
The remaining slots have fixed meanings depending on the type.
Additional slots may be added to types in future Pkl releases. Decoders *must* be designed to defensively discard values beyond the number of known slots for a type or provide meaningful error messages.
The array's length is the number of slots that are filled. For example, xref:{uri-stdlib-List}[List] is encoded as an MessagePack array with two elements.
@@ -46,16 +48,16 @@ The array's length is the number of slots that are filled. For example, xref:{ur
||code |type |description |type |description |type |description
|link:{uri-stdlib-Typed}[Typed], link:{uri-stdlib-Dynamic}[Dynamic]
|`0x1`
|`0x01`
|link:{uri-messagepack-str}[str]
|Fully qualified class name
|<<type-name-encoding,Class name>>
|link:{uri-messagepack-str}[str]
|Enclosing module URI
|link:{uri-messagepack-array}[array]
|Array of <<object-members,object members>>
|link:{uri-stdlib-Map}[Map]
|`0x2`
|`0x02`
|link:{uri-messagepack-map}[map]
|Map of `<value>` to `<value>`
|
@@ -64,7 +66,7 @@ The array's length is the number of slots that are filled. For example, xref:{ur
|
|link:{uri-stdlib-Mapping}[Mapping]
|`0x3`
|`0x03`
|link:{uri-messagepack-map}[map]
|Map of `<value>` to `<value>`
|
@@ -73,7 +75,7 @@ The array's length is the number of slots that are filled. For example, xref:{ur
|
|link:{uri-stdlib-List}[List]
|`0x4`
|`0x04`
|link:{uri-messagepack-array}[array]
|Array of `<value>`
|
@@ -82,7 +84,7 @@ The array's length is the number of slots that are filled. For example, xref:{ur
|
|link:{uri-stdlib-Listing}[Listing]
|`0x5`
|`0x05`
|link:{uri-messagepack-array}[array]
|Array of `<value>`
|
@@ -91,7 +93,7 @@ The array's length is the number of slots that are filled. For example, xref:{ur
|
|link:{uri-stdlib-Set}[Set]
|`0x6`
|`0x06`
|link:{uri-messagepack-array}[array]
|Array of `<value>`
|
@@ -100,7 +102,7 @@ The array's length is the number of slots that are filled. For example, xref:{ur
|
|link:{uri-stdlib-Duration}[Duration]
|`0x7`
|`0x07`
|{uri-messagepack-float}[float64]
|Duration value
|link:{uri-messagepack-str}[str]
@@ -109,7 +111,7 @@ The array's length is the number of slots that are filled. For example, xref:{ur
|
|link:{uri-stdlib-DataSize}[DataSize]
|`0x8`
|`0x08`
|link:{uri-messagepack-float}[float64]
|Value (float64)
|link:{uri-messagepack-str}[str]
@@ -118,7 +120,7 @@ The array's length is the number of slots that are filled. For example, xref:{ur
|
|link:{uri-stdlib-Pair}[Pair]
|`0x9`
|`0x09`
|`<value>`
|First value
|`<value>`
@@ -127,7 +129,7 @@ The array's length is the number of slots that are filled. For example, xref:{ur
|
|link:{uri-stdlib-IntSeq}[IntSeq]
|`0xA`
|`0x0A`
|link:{uri-messagepack-int}[int]
|Start
|link:{uri-messagepack-int}[int]
@@ -136,7 +138,7 @@ The array's length is the number of slots that are filled. For example, xref:{ur
|Step
|link:{uri-stdlib-Regex}[Regex]
|`0xB`
|`0x0B`
|link:{uri-messagepack-str}[str]
|Regex string representation
|
@@ -145,25 +147,25 @@ The array's length is the number of slots that are filled. For example, xref:{ur
|
|link:{uri-stdlib-Class}[Class]
|`0xC`
|
|
|
|
|`0x0C`
|link:{uri-messagepack-str}[str]
|<<type-name-encoding,Class name>>
|link:{uri-messagepack-str}[str]
|Module URI
|
|
|link:{uri-stdlib-TypeAlias}[TypeAlias]
|`0xD`
|
|
|
|
|`0x0D`
|link:{uri-messagepack-str}[str]
|<<type-name-encoding,TypeAlias name>>
|link:{uri-messagepack-str}[str]
|Module URI
|
|
|link:{uri-stdlib-Function}[Function]
|`0xE`
|`0x0E`
|
|
|
@@ -172,7 +174,7 @@ The array's length is the number of slots that are filled. For example, xref:{ur
|
|link:{uri-stdlib-Bytes}[Bytes]
|`0xF`
|`0x0F`
|link:{uri-messagepack-bin}[bin]
|Binary contents
|
@@ -181,6 +183,19 @@ The array's length is the number of slots that are filled. For example, xref:{ur
|
|===
[[type-name-encoding]]
[NOTE]
====
Type names have specific encoding rules:
* When the module URI is `pkl:base`:
** If the type name is `ModuleClass`, this type represents the module class of `pkl:base`.
** Otherwise, the type name corresponds to a type in `pkl:base`.
* For all other module URIs:
** When the type name contains `\#`, the string after the `#` character corresponds to a type in that module. The string before the `#` is the name of the module.
** Otherwise, the type name is the name of the module and represents the class of the module.
====
[[object-members]]
== Object Members
@@ -212,4 +227,3 @@ Like non-primitive values, object members are encoded as MessagePack arrays, whe
|`<value>`
|element value
|===

View File

@@ -47,5 +47,6 @@ org.junit.platform:junit-platform-commons:1.14.0=apiDependenciesMetadata,compile
org.junit.platform:junit-platform-engine:1.14.0=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.junit.platform:junit-platform-launcher:1.14.0=testRuntimeClasspath
org.junit:junit-bom:5.14.0=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.msgpack:msgpack-core:0.9.8=compileClasspath
org.opentest4j:opentest4j:1.3.0=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
empty=annotationProcessor,compileOnlyDependenciesMetadata,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDefExtensions,sourcesJar,testAnnotationProcessor,testApiDependenciesMetadata,testCompileOnlyDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDefExtensions

View File

@@ -28,6 +28,7 @@ dependencies {
api(libs.junitParams)
api(projects.pklCommons) // for convenience
implementation(libs.assertj)
implementation(libs.msgpack)
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 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.
@@ -13,13 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.server
package org.pkl.commons.test
import java.lang.IllegalStateException
import java.util.*
import org.msgpack.core.MessagePack
import org.msgpack.core.MessageUnpacker
import org.msgpack.value.ValueType
import org.pkl.core.util.yaml.YamlEmitter
/** Renders MessagePack structures in YAML. */
class MessagePackDebugRenderer(bytes: ByteArray) {
@@ -27,7 +26,6 @@ class MessagePackDebugRenderer(bytes: ByteArray) {
private val currIndent = StringBuilder("")
private val sb = StringBuilder()
private val indent = " "
private val yamlEmitter = YamlEmitter.create(sb, "1.2", indent)
private fun incIndent() {
currIndent.append(indent)
@@ -45,7 +43,7 @@ class MessagePackDebugRenderer(bytes: ByteArray) {
private fun renderKey() {
val mf = unpacker.nextFormat
when (mf.valueType!!) {
ValueType.STRING -> yamlEmitter.emit(unpacker.unpackString(), currIndent, true)
ValueType.STRING -> emitString(unpacker.unpackString())
ValueType.MAP,
ValueType.ARRAY -> {
sb.append("? ")
@@ -66,14 +64,14 @@ class MessagePackDebugRenderer(bytes: ByteArray) {
ValueType.FLOAT,
ValueType.BOOLEAN,
ValueType.NIL -> sb.append(unpacker.unpackValue().toJson())
ValueType.STRING -> yamlEmitter.emit(unpacker.unpackString(), currIndent, false)
ValueType.STRING -> emitString(unpacker.unpackString())
ValueType.ARRAY -> {
val size = unpacker.unpackArrayHeader()
if (size == 0) {
sb.append("[]")
return
}
for (i in 0 until size) {
repeat(size) {
newline()
sb.append("- ")
incIndent()
@@ -87,7 +85,7 @@ class MessagePackDebugRenderer(bytes: ByteArray) {
sb.append("{}")
return
}
for (i in 0 until size) {
repeat(size) {
newline()
renderKey()
incIndent()
@@ -95,7 +93,12 @@ class MessagePackDebugRenderer(bytes: ByteArray) {
decIndent()
}
}
ValueType.BINARY,
ValueType.BINARY -> {
// https://yaml.org/type/binary.html
sb.append("!!binary ")
val size = unpacker.unpackBinaryHeader()
emitString(Base64.getEncoder().encodeToString(unpacker.readPayload(size)))
}
ValueType.EXTENSION -> throw IllegalStateException("Unexpected value type ${mf.valueType}")
}
}
@@ -104,4 +107,75 @@ class MessagePackDebugRenderer(bytes: ByteArray) {
renderValue()
sb.toString().removePrefix("\n")
}
fun emitString(str: String) {
val newlineIndex = str.indexOf('\n')
if (newlineIndex < 0) {
emitSingleLineString(str)
} else {
emitMultiLineString(str, newlineIndex)
}
}
// adapted from org.pkl.core.util.yaml.YamlEmitter.emitSingleQuotedString
fun emitSingleLineString(str: String) {
sb.append('\'')
val singleQuoteIndex = str.indexOfFirst { it == '\'' }
if (singleQuoteIndex == -1) {
sb.append(str)
} else {
var start = 0
val length = str.length
for (i in singleQuoteIndex..<length) {
if (str[i] == '\'') {
sb.append(str, start, i).append("''")
start = i + 1
}
}
if (start < length) {
sb.append(str, start, length)
}
}
sb.append('\'')
}
// adapted from org.pkl.core.util.yaml.YamlEmitter.emitSingleQuotedString
fun emitMultiLineString(str: String, newlineIndex: Int) {
currIndent.append(indent)
sb.append('|')
if (str.first() == ' ') {
sb.append(indent.length)
}
val length = str.length
if (str.last() == '\n') {
if (length == 1 || str[length - 2] == '\n') {
sb.append('+')
}
} else {
sb.append('-')
}
sb.append('\n')
var start = 0
for (i in newlineIndex..<length) {
if (str[i] != '\n') continue
if (i == start) {
// don't add leading indent before newline
sb.append('\n')
} else {
sb.append(currIndent).append(str, start, i + 1)
}
start = i + 1
}
if (start < length) {
sb.append(currIndent).append(str, start, length)
}
currIndent.setLength(currIndent.length - indent.length)
}
}

View File

@@ -79,7 +79,7 @@ org.junit.platform:junit-platform-commons:1.14.0=testCompileClasspath,testImplem
org.junit.platform:junit-platform-engine:1.14.0=testRuntimeClasspath
org.junit.platform:junit-platform-launcher:1.14.0=testRuntimeClasspath
org.junit:junit-bom:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.msgpack:msgpack-core:0.9.8=pklCodegenJava,runtimeClasspath,testRuntimeClasspath
org.msgpack:msgpack-core:0.9.8=compileClasspath,pklCodegenJava,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.organicdesign:Paguro:3.10.3=pklCodegenJava,runtimeClasspath,testRuntimeClasspath
org.snakeyaml:snakeyaml-engine:2.10=pklCodegenJava,runtimeClasspath,testRuntimeClasspath

View File

@@ -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.
@@ -82,6 +82,7 @@ dependencies {
api(projects.pklCore)
implementation(libs.geantyref)
implementation(libs.msgpack)
testImplementation(libs.javaxInject)

View File

@@ -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.
@@ -15,9 +15,14 @@
*/
package org.pkl.config.java;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.Map;
import org.pkl.config.java.mapper.ConversionException;
import org.pkl.config.java.mapper.ValueMapper;
import org.pkl.core.Composite;
import org.pkl.core.Evaluator;
import org.pkl.core.PklBinaryDecoder;
/**
* A root, intermediate, or leaf node in a configuration tree. Child nodes can be obtained by name
@@ -67,4 +72,51 @@ public interface Config {
* @throws ConversionException if the value cannot be converted to the given type
*/
<T> T as(JavaType<T> type);
/**
* Decode a config from the supplied byte array.
*
* @return the encoded config
*/
static Config from(byte[] bytes, ValueMapper mapper) {
return makeConfig(PklBinaryDecoder.decode(bytes), mapper);
}
/**
* Decode a config from the supplied byte array using a preconfigured {@link ValueMapper}.
*
* @return the encoded config
*/
static Config from(byte[] bytes) {
return from(bytes, ValueMapper.preconfigured());
}
/**
* Decode a config from the supplied {@link InputStream} using a preconfigured {@link
* ValueMapper}.
*
* @return the encoded config
*/
static Config from(InputStream inputStream, ValueMapper mapper) {
return makeConfig(PklBinaryDecoder.decode(inputStream), mapper);
}
/**
* Decode a config from the supplied {@link InputStream}.
*
* @return the encoded config
*/
static Config from(InputStream inputStream) {
return from(inputStream, ValueMapper.preconfigured());
}
private static Config makeConfig(Object decoded, ValueMapper mapper) {
if (decoded instanceof Composite composite) {
return new CompositeConfig("", mapper, composite);
}
if (decoded instanceof Map<?, ?> map) {
return new MapConfig("", mapper, map);
}
return new LeafConfig("", mapper, decoded);
}
}

View File

@@ -0,0 +1,175 @@
/*
* 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.
* 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.config.java;
import static org.assertj.core.api.Assertions.*;
import java.nio.file.Path;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.pkl.config.java.mapper.Named;
import org.pkl.config.java.mapper.Types;
import org.pkl.core.PObject;
public abstract class AbstractConfigTest {
private final Config pigeonConfig = getPigeonConfig();
private final Config pigeonModuleConfig = getPigeonModuleConfig();
private final Config pairConfig = getPairConfig();
private final Config mapConfig = getMapConfig();
protected abstract Config getPigeonConfig();
protected abstract Config getPigeonModuleConfig();
protected abstract Config getPairConfig();
protected abstract Config getMapConfig();
@Test
public void navigate() {
var pigeon = pigeonConfig.get("pigeon");
assertThat(pigeon.getQualifiedName()).isEqualTo("pigeon");
assertThat(pigeon.getRawValue()).isInstanceOf(PObject.class);
var address = pigeon.get("address");
assertThat(address.getQualifiedName()).isEqualTo("pigeon.address");
assertThat(address.getRawValue()).isInstanceOf(PObject.class);
var street = address.get("street");
assertThat(street.getQualifiedName()).isEqualTo("pigeon.address.street");
assertThat(street.getRawValue()).isInstanceOf(String.class);
assertThat(street.as(String.class)).isEqualTo("Fuzzy St.");
}
@Test
public void navigateToNonExistingObjectChild() {
var pigeon = pigeonConfig.get("pigeon");
var t = catchThrowable(() -> pigeon.get("non-existing"));
assertThat(t)
.isInstanceOf(NoSuchChildException.class)
.hasMessageStartingWith(
"Node `pigeon` of type `pkl.base#Dynamic` "
+ "does not have a property named `non-existing`.");
}
@Test
public void navigateToNonExistingMapChild() {
var map = mapConfig.get("x");
var t = catchThrowable(() -> map.get("non-existing"));
assertThat(t)
.isInstanceOf(NoSuchChildException.class)
.hasMessageStartingWith(
"Node `x` of type `pkl.base#Map` does not have a key named `non-existing`.");
}
@Test
public void navigateToNonExistingLeafChild() {
var age = pigeonConfig.get("pigeon").get("age");
var t = catchThrowable(() -> age.get("non-existing"));
assertThat(t)
.isInstanceOf(NoSuchChildException.class)
.hasMessageStartingWith(
"Leaf node `pigeon.age` of type `pkl.base#Int` does not have a child named `non-existing`.");
}
@Test
public void convertObjectToPojoByType() {
Person pigeon = pigeonConfig.get("pigeon").as(Person.class);
checkPigeon(pigeon);
}
@Test
public void convertObjectToPojoByJavaType() {
var pigeon = pigeonConfig.get("pigeon").as(JavaType.of(Person.class));
checkPigeon(pigeon);
}
@Test
public void convertModuleToPojoByType() {
var pigeon = pigeonModuleConfig.as(Person.class);
checkPigeon(pigeon);
}
@Test
public void convertModuleToPojoByJavaType() {
var pigeon = pigeonModuleConfig.as(JavaType.of(Person.class));
checkPigeon(pigeon);
}
private void checkPigeon(Person pigeon) {
assertThat(pigeon).isNotNull();
assertThat(pigeon.age).isEqualTo(30);
assertThat(pigeon.friends).containsExactly("john", "mary");
assertThat(pigeon.address.street).isEqualTo("Fuzzy St.");
}
@Test
public void convertToParameterizedTypeByType() {
Pair<Path, Integer> pair =
pairConfig.get("x").as(Types.parameterizedType(Pair.class, Path.class, Integer.class));
checkPair(pair);
}
@Test
public void convertToParameterizedTypeByJavaType() {
var pair = pairConfig.get("x").as(new JavaType<Pair<Path, Integer>>() {});
checkPair(pair);
}
private void checkPair(Pair<?, ?> pair) {
assertThat(pair).isNotNull();
assertThat(pair.first).isEqualTo(Path.of("file/path"));
assertThat(pair.second).isEqualTo(42);
}
public static class Person {
final int age;
final List<String> friends;
final Address address;
public Person(
@Named("age") int age,
@Named("friends") List<String> friends,
@Named("address") Address address) {
this.age = age;
this.friends = friends;
this.address = address;
}
}
public static class Address {
final String street;
public Address(@Named("street") String street) {
this.street = street;
}
}
public static class Pair<S, T> {
final S first;
final T second;
public Pair(@Named("first") S first, @Named("second") T second) {
this.first = first;
this.second = second;
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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.
* 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.config.java;
import java.util.Base64;
public class ConfigPklBinaryDecoderTest extends AbstractConfigTest {
// generate via: pbpaste | ./pkl-cli/build/executable/jpkl eval /dev/stdin -f pkl-binary | base64
@Override
protected Config getPigeonConfig() {
// pigeon { age = 30; friends = List("john", "mary"); address { street = "Fuzzy St." } }
return Config.from(
Base64.getDecoder()
.decode(
"lAGkdGVzdNklZmlsZTovLy9Vc2Vycy9qYmFzY2gvc3JjL3BrbC90ZXN0LnBrbJGTEKZwaWdlb26UAadEeW5hbWljqHBrbDpiYXNlk5MQo2FnZR6TEKdmcmllbmRzkgSSpGpvaG6kbWFyeZMQp2FkZHJlc3OUAadEeW5hbWljqHBrbDpiYXNlkZMQpnN0cmVldKlGdXp6eSBTdC4="));
}
@Override
protected Config getPigeonModuleConfig() {
// age = 30; friends = List("john", "mary"); address { street = "Fuzzy St." }
return Config.from(
Base64.getDecoder()
.decode(
"lAGlc3RkaW6xZmlsZTovLy9kZXYvc3RkaW6TkxCjYWdlHpMQp2ZyaWVuZHOSBJKkam9obqRtYXJ5kxCnYWRkcmVzc5QBp0R5bmFtaWOocGtsOmJhc2WRkxCmc3RyZWV0qUZ1enp5IFN0Lg=="));
}
@Override
protected Config getPairConfig() {
// x { first = "file/path"; second = 42 }
return Config.from(
Base64.getDecoder()
.decode(
"lAGlc3RkaW6xZmlsZTovLy9kZXYvc3RkaW6RkxCheJQBp0R5bmFtaWOocGtsOmJhc2WSkxClZmlyc3SpZmlsZS9wYXRokxCmc2Vjb25kKg=="));
}
@Override
protected Config getMapConfig() {
// x = Map("one", 1, "two", 2)
return Config.from(
Base64.getDecoder().decode("lAGlc3RkaW6xZmlsZTovLy9kZXYvc3RkaW6RkxCheJICgqNvbmUBo3R3bwI="));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 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.
@@ -15,164 +15,31 @@
*/
package org.pkl.config.java;
import static org.assertj.core.api.Assertions.*;
import static org.pkl.core.ModuleSource.text;
import java.nio.file.Path;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.pkl.config.java.mapper.Named;
import org.pkl.config.java.mapper.Types;
import org.pkl.core.PObject;
public class ConfigTest extends AbstractConfigTest {
private static final ConfigEvaluator evaluator = ConfigEvaluator.preconfigured();
public class ConfigTest {
private final ConfigEvaluator evaluator = ConfigEvaluator.preconfigured();
private final Config pigeonConfig =
evaluator.evaluate(
text(
"pigeon { age = 30; friends = List(\"john\", \"mary\"); address { street = \"Fuzzy St.\" } }"));
private final Config pigeonModuleConfig =
evaluator.evaluate(
text("age = 30; friends = List(\"john\", \"mary\"); address { street = \"Fuzzy St.\" }"));
private final Config pairConfig =
evaluator.evaluate(text("x { first = \"file/path\"; second = 42 }"));
private final Config mapConfig = evaluator.evaluate(text("x = Map(\"one\", 1, \"two\", 2)"));
@Test
public void navigate() {
var pigeon = pigeonConfig.get("pigeon");
assertThat(pigeon.getQualifiedName()).isEqualTo("pigeon");
assertThat(pigeon.getRawValue()).isInstanceOf(PObject.class);
var address = pigeon.get("address");
assertThat(address.getQualifiedName()).isEqualTo("pigeon.address");
assertThat(address.getRawValue()).isInstanceOf(PObject.class);
var street = address.get("street");
assertThat(street.getQualifiedName()).isEqualTo("pigeon.address.street");
assertThat(street.getRawValue()).isInstanceOf(String.class);
assertThat(street.as(String.class)).isEqualTo("Fuzzy St.");
@Override
protected Config getPigeonConfig() {
return evaluator.evaluate(
text(
"pigeon { age = 30; friends = List(\"john\", \"mary\"); address { street = \"Fuzzy St.\" } }"));
}
@Test
public void navigateToNonExistingObjectChild() {
var pigeon = pigeonConfig.get("pigeon");
var t = catchThrowable(() -> pigeon.get("non-existing"));
assertThat(t)
.isInstanceOf(NoSuchChildException.class)
.hasMessageStartingWith(
"Node `pigeon` of type `pkl.base#Dynamic` "
+ "does not have a property named `non-existing`.");
@Override
protected Config getPigeonModuleConfig() {
return evaluator.evaluate(
text("age = 30; friends = List(\"john\", \"mary\"); address { street = \"Fuzzy St.\" }"));
}
@Test
public void navigateToNonExistingMapChild() {
var map = mapConfig.get("x");
var t = catchThrowable(() -> map.get("non-existing"));
assertThat(t)
.isInstanceOf(NoSuchChildException.class)
.hasMessageStartingWith(
"Node `x` of type `pkl.base#Map` does not have a key named `non-existing`.");
@Override
protected Config getPairConfig() {
return evaluator.evaluate(text("x { first = \"file/path\"; second = 42 }"));
}
@Test
public void navigateToNonExistingLeafChild() {
var age = pigeonConfig.get("pigeon").get("age");
var t = catchThrowable(() -> age.get("non-existing"));
assertThat(t)
.isInstanceOf(NoSuchChildException.class)
.hasMessageStartingWith(
"Leaf node `pigeon.age` of type `pkl.base#Int` does not have a child named `non-existing`.");
}
@Test
public void convertObjectToPojoByType() {
Person pigeon = pigeonConfig.get("pigeon").as(Person.class);
checkPigeon(pigeon);
}
@Test
public void convertObjectToPojoByJavaType() {
var pigeon = pigeonConfig.get("pigeon").as(JavaType.of(Person.class));
checkPigeon(pigeon);
}
@Test
public void convertModuleToPojoByType() {
var pigeon = pigeonModuleConfig.as(Person.class);
checkPigeon(pigeon);
}
@Test
public void convertModuleToPojoByJavaType() {
var pigeon = pigeonModuleConfig.as(JavaType.of(Person.class));
checkPigeon(pigeon);
}
private void checkPigeon(Person pigeon) {
assertThat(pigeon).isNotNull();
assertThat(pigeon.age).isEqualTo(30);
assertThat(pigeon.friends).containsExactly("john", "mary");
assertThat(pigeon.address.street).isEqualTo("Fuzzy St.");
}
@Test
public void convertToParameterizedTypeByType() {
Pair<Path, Integer> pair =
pairConfig.get("x").as(Types.parameterizedType(Pair.class, Path.class, Integer.class));
checkPair(pair);
}
@Test
public void convertToParameterizedTypeByJavaType() {
var pair = pairConfig.get("x").as(new JavaType<Pair<Path, Integer>>() {});
checkPair(pair);
}
private void checkPair(Pair<?, ?> pair) {
assertThat(pair).isNotNull();
assertThat(pair.first).isEqualTo(Path.of("file/path"));
assertThat(pair.second).isEqualTo(42);
}
public static class Person {
final int age;
final List<String> friends;
final Address address;
public Person(
@Named("age") int age,
@Named("friends") List<String> friends,
@Named("address") Address address) {
this.age = age;
this.friends = friends;
this.address = address;
}
}
public static class Address {
final String street;
public Address(@Named("street") String street) {
this.street = street;
}
}
public static class Pair<S, T> {
final S first;
final T second;
public Pair(@Named("first") S first, @Named("second") T second) {
this.first = first;
this.second = second;
}
@Override
protected Config getMapConfig() {
return evaluator.evaluate(text("x = Map(\"one\", 1, \"two\", 2)"));
}
}

View File

@@ -78,7 +78,7 @@ org.junit.platform:junit-platform-commons:1.14.0=testCompileClasspath,testImplem
org.junit.platform:junit-platform-engine:1.14.0=testRuntimeClasspath
org.junit.platform:junit-platform-launcher:1.14.0=testRuntimeClasspath
org.junit:junit-bom:5.14.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.msgpack:msgpack-core:0.9.8=pklCodegenKotlin,testRuntimeClasspath
org.msgpack:msgpack-core:0.9.8=compileClasspath,pklCodegenKotlin,pklConfigJava,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.organicdesign:Paguro:3.10.3=pklCodegenKotlin,testRuntimeClasspath
org.snakeyaml:snakeyaml-engine:2.10=pklCodegenKotlin,testRuntimeClasspath

View File

@@ -30,6 +30,7 @@ val buildInfo = project.extensions.getByType<BuildInfo>()
dependencies {
pklCodegenKotlin(projects.pklCodegenKotlin)
implementation(libs.kotlinReflect)
implementation(libs.msgpack)
// Don't declare a runtime dependency to pkl-config-java because Gradle cannot resolve
// the correct publication (library vs fatJar) when generating the POM.

View File

@@ -172,6 +172,15 @@ public interface Evaluator extends AutoCloseable {
*/
Object evaluateExpression(ModuleSource moduleSource, String expression);
/**
* Evaluates the Pkl expression represented as {@code expression}, returning a byte array of the
* <code>pkl-binary</code>-encoded representation of the result.
*
* @throws PklException if an error occurs during evaluation
* @throws IllegalStateException if this evaluator has already been closed
*/
byte[] evaluateExpressionPklBinary(ModuleSource moduleSource, String expression);
/**
* Evaluates the Pkl expression, returning the stringified result.
*

View File

@@ -28,6 +28,8 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
import org.graalvm.polyglot.Context;
import org.msgpack.core.MessageBufferPacker;
import org.msgpack.core.MessagePack;
import org.pkl.core.ast.ConstantValueNode;
import org.pkl.core.ast.internal.ToStringNodeGen;
import org.pkl.core.evaluatorSettings.TraceMode;
@@ -48,6 +50,7 @@ import org.pkl.core.runtime.VmExceptionBuilder;
import org.pkl.core.runtime.VmLanguage;
import org.pkl.core.runtime.VmMapping;
import org.pkl.core.runtime.VmNull;
import org.pkl.core.runtime.VmPklBinaryEncoder;
import org.pkl.core.runtime.VmStackOverflowException;
import org.pkl.core.runtime.VmTyped;
import org.pkl.core.runtime.VmUtils;
@@ -56,17 +59,18 @@ import org.pkl.core.runtime.VmValueRenderer;
import org.pkl.core.util.ErrorMessages;
import org.pkl.core.util.Nullable;
public class EvaluatorImpl implements Evaluator {
protected final StackFrameTransformer frameTransformer;
protected final boolean color;
protected final ModuleResolver moduleResolver;
protected final Context polyglotContext;
protected final @Nullable Duration timeout;
protected final @Nullable ScheduledExecutorService timeoutExecutor;
protected final SecurityManager securityManager;
protected final BufferedLogger logger;
protected final PackageResolver packageResolver;
public final class EvaluatorImpl implements Evaluator {
private final StackFrameTransformer frameTransformer;
private final boolean color;
private final ModuleResolver moduleResolver;
private final Context polyglotContext;
private final @Nullable Duration timeout;
private final @Nullable ScheduledExecutorService timeoutExecutor;
private final SecurityManager securityManager;
private final BufferedLogger logger;
private final PackageResolver packageResolver;
private final VmValueRenderer vmValueRenderer = VmValueRenderer.singleLine(1000);
private @Nullable MessageBufferPacker messagePacker;
public EvaluatorImpl(
StackFrameTransformer transformer,
@@ -217,6 +221,37 @@ public class EvaluatorImpl implements Evaluator {
};
}
private MessageBufferPacker getMessagePacker() {
if (messagePacker == null) {
messagePacker = MessagePack.newDefaultBufferPacker();
}
messagePacker.clear();
return messagePacker;
}
@Override
public byte[] evaluateExpressionPklBinary(ModuleSource moduleSource, String expression) {
return doEvaluate(
moduleSource,
(module) -> {
var expressionResult =
switch (expression) {
case "module" -> module;
case "output.text" -> VmUtils.readTextProperty(readModuleOutput(module));
case "output.value" ->
VmUtils.readMember(readModuleOutput(module), Identifier.VALUE);
case "output.bytes" -> VmUtils.readBytesProperty(readModuleOutput(module));
default ->
VmUtils.evaluateExpression(module, expression, securityManager, moduleResolver);
};
VmValue.force(expressionResult, false);
var packer = getMessagePacker();
new VmPklBinaryEncoder(packer).renderDocument(expressionResult);
return packer.toByteArray();
});
}
@Override
public String evaluateExpressionString(ModuleSource moduleSource, String expression) {
// optimization: if the expression is `output.text` (the common case), read members
@@ -363,7 +398,7 @@ public class EvaluatorImpl implements Evaluator {
return evalResult;
}
protected <T> T doEvaluate(ModuleSource moduleSource, Function<VmTyped, T> doEvaluate) {
private <T> T doEvaluate(ModuleSource moduleSource, Function<VmTyped, T> doEvaluate) {
return doEvaluate(
() -> {
var moduleKey = moduleResolver.resolve(moduleSource);

View File

@@ -0,0 +1,184 @@
/*
* Copyright © 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.
* 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;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.msgpack.core.MessagePack;
import org.msgpack.core.MessageUnpacker;
import org.pkl.core.runtime.BaseModule;
import org.pkl.core.util.CollectionUtils;
import org.pkl.core.util.pklbinary.AbstractPklBinaryDecoder;
/**
* A decoder/parser for <code>pkl-binary</code>.
*
* <p>For how pkl-binary turns Java, see {@link Value}.
*/
public class PklBinaryDecoder extends AbstractPklBinaryDecoder {
private PklBinaryDecoder(MessageUnpacker unpacker) {
super(unpacker);
}
/** Decode a value from the supplied byte array. */
public static Object decode(byte[] bytes) {
return new PklBinaryDecoder(MessagePack.newDefaultUnpacker(bytes)).decode();
}
/** Decode a value from the supplied {@link InputStream}. */
public static Object decode(InputStream inputStream) {
return new PklBinaryDecoder(MessagePack.newDefaultUnpacker(inputStream)).decode();
}
@Override
protected RuntimeException doFail(Exception cause, long offset, List<String> path) {
return new RuntimeException(
String.format(
"Exception while decoding binary data at offset %d, path [%s]",
offset, String.join(", ", path)),
cause);
}
@Override
protected RuntimeException doIOFail(IOException cause) {
return new UncheckedIOException("IO exception during decoding", cause);
}
@Override
protected Object doDecodeNull() {
return PNull.getInstance();
}
@Override
protected Object doDecodeDuration(double value, DurationUnit unit) {
return new Duration(value, unit);
}
@Override
protected Object doDecodeObject(
String className, URI moduleUri, DecodeIterator<DecodedObjectMember> iter) {
var properties = CollectionUtils.<String, Object>newLinkedHashMap(iter.getSize());
while (iter.hasNext()) {
var member = iter.next();
properties.put(member.key().toString(), member.value());
}
if (moduleUri.equals(PClassInfo.pklBaseUri)) {
// dynamic
if (className.equals(BaseModule.getDynamicClass().getDisplayName())) {
return new PObject(PClassInfo.Dynamic, properties);
}
// pkl:base typed
if (!className.equals(BaseModule.getModule().getVmClass().getDisplayName())) {
return new PObject(PClassInfo.get("pkl.base", className, moduleUri), properties);
}
// fall through to module case
}
// module
var hashIndex = className.lastIndexOf("#");
if (hashIndex < 0) {
return new PModule(
moduleUri, className, PClassInfo.get(className, "ModuleClass", moduleUri), properties);
}
// non-pkl:base class
return new PObject(
PClassInfo.get(
className.substring(0, hashIndex), className.substring(hashIndex + 1), moduleUri),
properties);
}
@Override
protected Object doDecodeMap(MapDecodeIterator iter) {
var map = CollectionUtils.newLinkedHashMap(iter.getSize());
while (iter.hasNext()) {
var entry = iter.next();
map.put(entry.getFirst(), entry.getSecond());
}
return map;
}
@Override
protected Object doDecodeMapping(MapDecodeIterator iter) {
return doDecodeMap(iter); // same exported result!
}
@Override
protected Object doDecodeList(CollectionDecodeIterator iter) {
var listing = new ArrayList<>(iter.getSize());
while (iter.hasNext()) {
listing.add(iter.next());
}
return listing;
}
@Override
protected Object doDecodeListing(CollectionDecodeIterator iter) {
return doDecodeList(iter); // same exported result!
}
@Override
protected Object doDecodeSet(CollectionDecodeIterator iter) {
var set = CollectionUtils.newLinkedHashSet(iter.getSize());
while (iter.hasNext()) {
set.add(iter.next());
}
return set;
}
@Override
protected Object doDecodeDataSize(double value, DataSizeUnit unit) {
return new DataSize(value, unit);
}
@Override
protected Object doDecodePair(Object first, Object second) {
return new Pair<>(first, second);
}
@Override
protected Object doDecodeIntSeq(long start, long end, long step) {
throw new DecodeException("Cannot decode IntSeq value");
}
@Override
protected Object doDecodeRegex(Pattern pattern) {
return pattern;
}
@Override
protected Object doDecodeClass(String qualifiedName, URI moduleUri) {
throw new DecodeException("Cannot decode Class value");
}
@Override
protected Object doDecodeTypeAlias(String qualifiedName, URI moduleUri) {
throw new DecodeException("Cannot decode TypeAlias value");
}
@Override
protected Object doDecodeBytes(byte[] bytes) {
return bytes;
}
}

View File

@@ -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.
@@ -53,7 +53,7 @@ public final class TypeAlias extends Member implements Value {
/**
* Returns the name of the module that this type alias is declared in. Note that a module name is
* not guaranteed to be unique, especially if it not declared but inferred from the module URI.
* not guaranteed to be unique, especially if it is not declared but inferred from the module URI.
*/
public String getModuleName() {
return moduleName;

View File

@@ -143,6 +143,9 @@ public final class Identifier implements Comparable<Identifier> {
// members of pkl.yaml
public static final Identifier MAX_COLLECTION_ALIASES = get("maxCollectionAliases");
// members of pkl.encoding
public static final Identifier IMPORTS = get("imports");
// common in lambdas etc
public static final Identifier IT = get("it");

View File

@@ -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.
@@ -92,6 +92,8 @@ public final class ModuleCache {
return BaseModule.getModule();
case "Benchmark":
return BenchmarkModule.getModule();
case "pklbinary":
return PklBinaryModule.getModule();
case "jsonnet":
return JsonnetModule.getModule();
case "math":

View File

@@ -0,0 +1,30 @@
/*
* Copyright © 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.
* 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.net.URI;
public class PklBinaryModule extends StdLibModule {
private static final VmTyped instance = VmUtils.createEmptyModule();
static {
loadModule(URI.create("pkl:pklbinary"), instance);
}
public static VmTyped getModule() {
return instance;
}
}

View File

@@ -0,0 +1,376 @@
/*
* Copyright © 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.
* 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.io.IOException;
import java.util.Deque;
import org.msgpack.core.MessageBufferPacker;
import org.pkl.core.PklBugException;
import org.pkl.core.stdlib.AbstractRenderer;
import org.pkl.core.stdlib.PklConverter;
import org.pkl.core.util.pklbinary.PklBinaryCode;
/** An encoder/renderer for <code>pkl-binary</code></a> encoding. */
public class VmPklBinaryEncoder extends AbstractRenderer {
// this type explicitly works with MessageBufferPacker:
// * assumes no I/O during packing (in-memory writes only)
// * IOExceptions are caught and assumed unreachable
private final MessageBufferPacker packer;
public VmPklBinaryEncoder(MessageBufferPacker packer, PklConverter converter) {
super("pkl-binary", converter, false, false);
this.packer = packer;
}
public VmPklBinaryEncoder(MessageBufferPacker packer) {
this(packer, new PklConverter(VmMapping.empty()));
}
private void packCode(PklBinaryCode code) throws IOException {
packer.packByte(code.getCode());
}
@Override
public void visitString(String value) {
try {
packer.packString(value);
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
}
@Override
public void visitBoolean(Boolean value) {
try {
packer.packBoolean(value);
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
}
@Override
public void visitInt(Long value) {
try {
packer.packLong(value);
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
}
@Override
public void visitFloat(Double value) {
try {
packer.packDouble(value);
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
}
@Override
public void visitDuration(VmDuration value) {
try {
packer.packArrayHeader(3);
packCode(PklBinaryCode.DURATION);
packer.packDouble(value.getValue());
packer.packString(value.getUnit().toString());
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
}
@Override
public void visitDataSize(VmDataSize value) {
try {
packer.packArrayHeader(3);
packCode(PklBinaryCode.DATASIZE);
packer.packDouble(value.getValue());
packer.packString(value.getUnit().toString());
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
}
@Override
public void visitBytes(VmBytes value) {
try {
packer.packArrayHeader(2);
packCode(PklBinaryCode.BYTES);
packer.packBinaryHeader(value.getBytes().length);
packer.addPayload(value.getBytes());
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
}
@Override
public void visitIntSeq(VmIntSeq value) {
try {
packer.packArrayHeader(4);
packCode(PklBinaryCode.INTSEQ);
packer.packLong(value.start);
packer.packLong(value.end);
packer.packLong(value.step);
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
}
@Override
protected void visitDocument(Object value) {
visit(value);
}
@Override
protected void visitTopLevelValue(Object value) {
visit(value);
}
@Override
protected void visitRenderDirective(VmTyped value) {
try {
packer.writePayload(VmUtils.readBytesProperty(value).getBytes());
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
}
@Override
protected void startDynamic(VmDynamic value) {
startObject(value, value.getRegularMemberCount());
}
@Override
protected void startTyped(VmTyped value) {
startObject(value, value.getVmClass().getAllRegularPropertyNames().size());
}
private void startObject(VmObjectLike value, int memberCount) {
try {
packer.packArrayHeader(4);
packCode(PklBinaryCode.OBJECT);
packer.packString(value.getVmClass().getDisplayName());
packer.packString(
value.getVmClass().getModule().getModuleInfo().getModuleKey().getUri().toString());
packer.packArrayHeader(memberCount);
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
}
private void startList(PklBinaryCode code, int length) {
try {
packer.packArrayHeader(2);
packCode(code);
packer.packArrayHeader(length);
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
}
private void startMap(PklBinaryCode code, int length) {
try {
packer.packArrayHeader(2);
packCode(code);
packer.packMapHeader(length);
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
}
@Override
protected void startListing(VmListing value) {
startList(PklBinaryCode.LISTING, value.getLength());
}
@Override
protected void startMapping(VmMapping value) {
startMap(PklBinaryCode.MAPPING, (int) value.getLength());
}
@Override
protected void startList(VmList value) {
startList(PklBinaryCode.LIST, value.getLength());
}
@Override
protected void startSet(VmSet value) {
startList(PklBinaryCode.SET, value.getLength());
}
@Override
protected void startMap(VmMap value) {
startMap(PklBinaryCode.MAP, value.getLength());
}
@Override
protected void visitEntryKeyValue(
Object key, boolean isFirst, Deque<Object> valuePath, Object value) {
if (enclosingValue instanceof VmDynamic) {
try {
packer.packArrayHeader(3);
packCode(PklBinaryCode.ENTRY);
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
}
super.visitEntryKeyValue(key, isFirst, valuePath, value);
}
@Override
protected void visitElement(long index, Object value, boolean isFirst) {
if (enclosingValue instanceof VmDynamic) {
try {
packer.packArrayHeader(3);
packCode(PklBinaryCode.ELEMENT);
packer.packLong(index);
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
}
visit(value);
}
@Override
protected void visitProperty(Identifier name, Object value, boolean isFirst) {
try {
packer.packArrayHeader(3);
packCode(PklBinaryCode.PROPERTY);
packer.packString(name.toString());
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
visit(value);
}
@Override
public void visitClass(VmClass value) {
try {
packer.packArrayHeader(3);
packCode(PklBinaryCode.CLASS);
packer.packString(value.getDisplayName());
packer.packString(value.getModule().getModuleInfo().getModuleKey().getUri().toString());
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
}
@Override
public void visitTypeAlias(VmTypeAlias value) {
try {
packer.packArrayHeader(3);
packCode(PklBinaryCode.TYPEALIAS);
packer.packString(value.getDisplayName());
packer.packString(value.getModuleUri().toString());
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
}
@Override
public void visitPair(VmPair value) {
try {
packer.packArrayHeader(3);
packCode(PklBinaryCode.PAIR);
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
visit(value.getFirst());
visit(value.getSecond());
}
@Override
public void visitRegex(VmRegex value) {
try {
packer.packArrayHeader(2);
packCode(PklBinaryCode.REGEX);
packer.packString(value.getPattern().pattern());
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
}
@Override
public void visitNull(VmNull value) {
try {
packer.packNil();
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
}
@Override
public void visitFunction(VmFunction value) {
try {
packer.packArrayHeader(1);
packCode(PklBinaryCode.FUNCTION);
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
}
@Override
protected void visitEntryKey(Object key, boolean isFirst) {
visit(key);
}
@Override
protected void visitEntryValue(Object value) {
visit(value);
}
@Override
protected void endDynamic(VmDynamic value, boolean isEmpty) {
// noop
}
@Override
protected void endTyped(VmTyped value, boolean isEmpty) {
// noop
}
@Override
protected void endListing(VmListing value, boolean isEmpty) {
// noop
}
@Override
protected void endMapping(VmMapping value, boolean isEmpty) {
// noop
}
@Override
protected void endList(VmList value) {
// noop
}
@Override
protected void endSet(VmSet value) {
// noop
}
@Override
protected void endMap(VmMap value) {
// noop
}
@Override
protected boolean canRenderPropertyOrEntryOf(VmDynamic object) {
return true;
}
}

View File

@@ -19,10 +19,12 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.frame.Frame;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.source.SourceSection;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.concurrent.GuardedBy;
import org.pkl.core.Member.SourceLocation;
import org.pkl.core.PClassInfo;
import org.pkl.core.PObject;
import org.pkl.core.TypeAlias;
import org.pkl.core.TypeParameter;
@@ -140,6 +142,10 @@ public final class VmTypeAlias extends VmValue {
return module.getVmClass().getModuleName();
}
public URI getModuleUri() {
return module.getVmClass().getPClassInfo().getModuleUri();
}
public VmTyped getModuleMirror() {
return module.getModuleInfo().getMirror(module);
}
@@ -152,6 +158,12 @@ public final class VmTypeAlias extends VmValue {
return qualifiedName;
}
public String getDisplayName() {
// display `UInt` rather than `pkl.base#UInt`, etc.
// based on PClassInfo.getDisplayName
return getModuleUri().equals(PClassInfo.pklBaseUri) ? simpleName : qualifiedName;
}
public int getTypeParameterCount() {
return typeParameters.size();
}

View File

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

View File

@@ -406,14 +406,6 @@ public final class VmUtils {
return isCustomThisScope ? new CustomThisNode(sourceSection) : new ThisNode(sourceSection);
}
public static boolean isRenderDirective(VmValue value) {
return value.getVmClass() == BaseModule.getRenderDirectiveClass();
}
public static boolean isRenderDirective(Object value) {
return value instanceof VmTyped typed && isRenderDirective(typed);
}
public static boolean isPcfRenderDirective(Object value) {
return value instanceof VmTyped typed
&& typed.getVmClass().getPClassInfo() == PClassInfo.PcfRenderDirective;

View File

@@ -19,6 +19,7 @@ import com.oracle.truffle.api.source.SourceSection;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import org.pkl.core.runtime.BaseModule;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmClass;
import org.pkl.core.runtime.VmCollection;
@@ -35,7 +36,6 @@ import org.pkl.core.runtime.VmSet;
import org.pkl.core.runtime.VmTypeAlias;
import org.pkl.core.runtime.VmTyped;
import org.pkl.core.runtime.VmUndefinedValueException;
import org.pkl.core.runtime.VmUtils;
import org.pkl.core.runtime.VmValue;
import org.pkl.core.runtime.VmValueConverter;
import org.pkl.core.runtime.VmValueVisitor;
@@ -43,18 +43,11 @@ import org.pkl.core.util.LateInit;
import org.pkl.core.util.MutableBoolean;
import org.pkl.core.util.Nullable;
/** Base class for renderers that are part of the standard library. */
public abstract class AbstractRenderer implements VmValueVisitor {
protected static final char LINE_BREAK = '\n';
/** The name of this renderer. */
protected final String name;
protected final StringBuilder builder;
/** The indent to be used. */
protected final String indent;
protected final PklConverter converter;
/** Whether to skip visiting properties with null value (after conversion). */
@@ -65,33 +58,31 @@ public abstract class AbstractRenderer implements VmValueVisitor {
@LateInit protected Deque<Object> currPath;
/** The value directly enclosing the currently visited value, if any. */
protected @Nullable VmValue enclosingValue;
/** The value passed to either {@link #renderDocument(Object)} or {@link #renderValue(Object)}. */
@LateInit private Object topLevelValue;
/** The current indent. Modified by {@link #increaseIndent()} and {@link #decreaseIndent()}. */
protected final StringBuilder currIndent = new StringBuilder();
/** The value directly enclosing the currently visited value, if any. */
protected @Nullable VmValue enclosingValue;
/** The (closest) {@link SourceSection} of the value being visited, for better error messages. */
protected @Nullable SourceSection currSourceSection = null;
public AbstractRenderer(
String name,
StringBuilder builder,
String indent,
PklConverter converter,
boolean skipNullProperties,
boolean skipNullEntries) {
protected AbstractRenderer(
String name, PklConverter converter, boolean skipNullProperties, boolean skipNullEntries) {
this.name = name;
this.builder = builder;
this.indent = indent;
this.converter = converter;
this.skipNullProperties = skipNullProperties;
this.skipNullEntries = skipNullEntries;
}
protected boolean isRenderDirective(VmValue value) {
return value.getVmClass() == BaseModule.getRenderDirectiveClass();
}
protected boolean isRenderDirective(Object value) {
return value instanceof VmTyped typed && isRenderDirective(typed);
}
public final void renderDocument(Object value) {
currPath = new ArrayDeque<>();
currPath.push(VmValueConverter.TOP_LEVEL_VALUE);
@@ -185,71 +176,22 @@ public abstract class AbstractRenderer implements VmValueVisitor {
protected abstract void endMap(VmMap value);
private void doVisitProperty(
Identifier name, Object value, SourceSection sourceSection, MutableBoolean isFirst) {
var prevSourceSection = currSourceSection;
currSourceSection = sourceSection;
currPath.push(name);
var convertedValue = converter.convert(value, currPath);
if (!(skipNullProperties && convertedValue instanceof VmNull)) {
visitProperty(name, convertedValue, isFirst.getAndSetFalse());
}
currPath.pop();
currSourceSection = prevSourceSection;
}
private void doVisitEntry(
Object key, Object value, @Nullable SourceSection sourceSection, MutableBoolean isFirst) {
var prevSourceSection = currSourceSection;
if (sourceSection != null) {
currSourceSection = sourceSection;
}
var valuePath = currPath;
try {
var convertedKey = converter.convert(key, List.of());
valuePath.push(convertedKey);
var convertedValue = converter.convert(value, valuePath);
if (skipNullEntries && (convertedValue instanceof VmNull)) {
return;
}
currPath = new ArrayDeque<>();
visitEntryKey(convertedKey, isFirst.getAndSetFalse());
currPath = valuePath;
visitEntryValue(convertedValue);
} finally {
valuePath.pop();
currSourceSection = prevSourceSection;
}
}
private void doVisitElement(
long index, Object value, @Nullable SourceSection sourceSection, boolean isFirst) {
var prevSourceSection = currSourceSection;
if (sourceSection != null) {
currSourceSection = sourceSection;
}
currPath.push(index);
var convertedValue = converter.convert(value, currPath);
visitElement(index, convertedValue, isFirst);
currPath.pop();
currSourceSection = prevSourceSection;
}
@Override
public void visitTyped(VmTyped value) {
// value.getParent().getMember(value);
if (VmUtils.isRenderDirective(value)) {
if (isRenderDirective(value)) {
visitRenderDirective(value);
return;
}
value.force(false, false);
startTyped(value);
var prevEnclosingValue = enclosingValue;
enclosingValue = value;
var isFirst = new MutableBoolean(true);
value.forceAndIterateMemberValues(
value.iterateAlreadyForcedMemberValues(
(memberKey, member, memberValue) -> {
if (member.isClass() || member.isTypeAlias()) return true;
assert member.isProp();
@@ -263,6 +205,7 @@ public abstract class AbstractRenderer implements VmValueVisitor {
@Override
public final void visitDynamic(VmDynamic value) {
value.force(false, false);
startDynamic(value);
var prevEnclosingValue = enclosingValue;
@@ -270,7 +213,7 @@ public abstract class AbstractRenderer implements VmValueVisitor {
var isFirst = new MutableBoolean(true);
var canRenderPropertyOrEntry = canRenderPropertyOrEntryOf(value);
value.forceAndIterateMemberValues(
value.iterateAlreadyForcedMemberValues(
(memberKey, member, memberValue) -> {
var sourceSection = member.getSourceSection();
if (member.isProp()) {
@@ -302,12 +245,14 @@ public abstract class AbstractRenderer implements VmValueVisitor {
@Override
public final void visitListing(VmListing value) {
value.force(false, false);
startListing(value);
var prevEnclosingValue = enclosingValue;
enclosingValue = value;
var isFirst = new MutableBoolean(true);
value.forceAndIterateMemberValues(
value.iterateAlreadyForcedMemberValues(
(memberKey, member, memberValue) -> {
assert member.isElement();
doVisitElement(
@@ -321,12 +266,14 @@ public abstract class AbstractRenderer implements VmValueVisitor {
@Override
public final void visitMapping(VmMapping value) {
value.force(false, false);
startMapping(value);
var prevEnclosingValue = enclosingValue;
enclosingValue = value;
var isFirst = new MutableBoolean(true);
value.forceAndIterateMemberValues(
value.iterateAlreadyForcedMemberValues(
(memberKey, member, memberValue) -> {
assert member.isEntry();
doVisitEntry(memberKey, memberValue, member.getSourceSection(), isFirst);
@@ -379,19 +326,60 @@ public abstract class AbstractRenderer implements VmValueVisitor {
endMap(value);
}
@Override
public final void visitTypeAlias(VmTypeAlias value) {
cannotRenderTypeAddConverter(value);
private void doVisitProperty(
Identifier name, Object value, SourceSection sourceSection, MutableBoolean isFirst) {
var prevSourceSection = currSourceSection;
currSourceSection = sourceSection;
currPath.push(name);
var convertedValue = converter.convert(value, currPath);
if (!(skipNullProperties && convertedValue instanceof VmNull)) {
visitProperty(name, convertedValue, isFirst.getAndSetFalse());
}
currPath.pop();
currSourceSection = prevSourceSection;
}
@Override
public final void visitClass(VmClass value) {
cannotRenderTypeAddConverter(value);
private void doVisitEntry(
Object key, Object value, @Nullable SourceSection sourceSection, MutableBoolean isFirst) {
var prevSourceSection = currSourceSection;
if (sourceSection != null) {
currSourceSection = sourceSection;
}
var valuePath = currPath;
try {
var convertedKey = converter.convert(key, List.of());
valuePath.push(convertedKey);
var convertedValue = converter.convert(value, valuePath);
if (skipNullEntries && (convertedValue instanceof VmNull)) {
return;
}
visitEntryKeyValue(convertedKey, isFirst.getAndSetFalse(), valuePath, convertedValue);
} finally {
valuePath.pop();
currSourceSection = prevSourceSection;
}
}
@Override
public final void visitFunction(VmFunction value) {
cannotRenderTypeAddConverter(value);
protected void visitEntryKeyValue(
Object key, boolean isFirst, Deque<Object> valuePath, Object value) {
currPath = new ArrayDeque<>();
visitEntryKey(key, isFirst);
currPath = valuePath;
visitEntryValue(value);
}
private void doVisitElement(
long index, Object value, @Nullable SourceSection sourceSection, boolean isFirst) {
var prevSourceSection = currSourceSection;
if (sourceSection != null) {
currSourceSection = sourceSection;
}
currPath.push(index);
var convertedValue = converter.convert(value, currPath);
visitElement(index, convertedValue, isFirst);
currPath.pop();
currSourceSection = prevSourceSection;
}
protected void cannotRenderTypeAddConverter(VmValue value) {
@@ -405,6 +393,21 @@ public abstract class AbstractRenderer implements VmValueVisitor {
throw builder.build();
}
@Override
public void visitTypeAlias(VmTypeAlias value) {
cannotRenderTypeAddConverter(value);
}
@Override
public void visitClass(VmClass value) {
cannotRenderTypeAddConverter(value);
}
@Override
public void visitFunction(VmFunction value) {
cannotRenderTypeAddConverter(value);
}
protected void cannotRenderNonStringKey(Object key) {
assert enclosingValue != null;
var isMap = enclosingValue instanceof VmMap;
@@ -424,12 +427,4 @@ public abstract class AbstractRenderer implements VmValueVisitor {
.withProgramValue("Key", key)
.build();
}
protected void increaseIndent() {
currIndent.append(indent);
}
protected void decreaseIndent() {
currIndent.setLength(currIndent.length() - indent.length());
}
}

View File

@@ -0,0 +1,70 @@
/*
* 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.
* 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.stdlib;
import org.pkl.core.runtime.VmClass;
import org.pkl.core.runtime.VmFunction;
import org.pkl.core.runtime.VmTypeAlias;
/** Base class for renderers that are part of the standard library. */
public abstract class AbstractStringRenderer extends AbstractRenderer {
protected static final char LINE_BREAK = '\n';
protected final StringBuilder builder;
/** The indent to be used. */
protected final String indent;
/** The current indent. Modified by {@link #increaseIndent()} and {@link #decreaseIndent()}. */
protected final StringBuilder currIndent = new StringBuilder();
public AbstractStringRenderer(
String name,
StringBuilder builder,
String indent,
PklConverter converter,
boolean skipNullProperties,
boolean skipNullEntries) {
super(name, converter, skipNullProperties, skipNullEntries);
this.builder = builder;
this.indent = indent;
}
protected void increaseIndent() {
currIndent.append(indent);
}
protected void decreaseIndent() {
currIndent.setLength(currIndent.length() - indent.length());
}
// override these to mark them final
@Override
public final void visitTypeAlias(VmTypeAlias value) {
super.visitTypeAlias(value);
}
@Override
public final void visitClass(VmClass value) {
super.visitClass(value);
}
@Override
public final void visitFunction(VmFunction value) {
super.visitFunction(value);
}
}

View File

@@ -18,7 +18,7 @@ package org.pkl.core.stdlib.base;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Specialization;
import org.pkl.core.runtime.*;
import org.pkl.core.stdlib.AbstractRenderer;
import org.pkl.core.stdlib.AbstractStringRenderer;
import org.pkl.core.stdlib.ExternalMethod1Node;
import org.pkl.core.stdlib.PklConverter;
import org.pkl.core.util.json.JsonEscaper;
@@ -57,7 +57,7 @@ public final class JsonRendererNodes {
return new JsonRenderer(builder, indent, converter, omitNullProperties);
}
private static final class JsonRenderer extends AbstractRenderer {
private static final class JsonRenderer extends AbstractStringRenderer {
private final String separator;
private final JsonEscaper escaper = new JsonEscaper(false);
@@ -212,7 +212,7 @@ public final class JsonRendererNodes {
return;
}
if (VmUtils.isRenderDirective(key)) {
if (isRenderDirective(key)) {
visitRenderDirective((VmTyped) key);
builder.append(separator);
return;

View File

@@ -18,7 +18,7 @@ package org.pkl.core.stdlib.base;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Specialization;
import org.pkl.core.runtime.*;
import org.pkl.core.stdlib.AbstractRenderer;
import org.pkl.core.stdlib.AbstractStringRenderer;
import org.pkl.core.stdlib.ExternalMethod1Node;
import org.pkl.core.stdlib.PklConverter;
import org.pkl.core.util.ArrayCharEscaper;
@@ -54,7 +54,7 @@ public final class PListRendererNodes {
}
// keep in sync with org.pkl.core.PListRenderer
private static final class PListRenderer extends AbstractRenderer {
private static final class PListRenderer extends AbstractStringRenderer {
// it's safe (though not required) to escape all the following characters in XML text nodes
private static final ArrayCharEscaper charEscaper =
@@ -241,7 +241,7 @@ public final class PListRendererNodes {
builder.append("<dict>").append(LINE_BREAK);
}
if (VmUtils.isRenderDirective(key)) {
if (isRenderDirective(key)) {
key = VmUtils.readTextProperty(key);
}

View File

@@ -34,12 +34,12 @@ import org.pkl.core.runtime.VmRegex;
import org.pkl.core.runtime.VmSet;
import org.pkl.core.runtime.VmTyped;
import org.pkl.core.runtime.VmUtils;
import org.pkl.core.stdlib.AbstractRenderer;
import org.pkl.core.stdlib.AbstractStringRenderer;
import org.pkl.core.stdlib.PklConverter;
import org.pkl.core.util.LateInit;
import org.pkl.parser.Lexer;
public final class PcfRenderer extends AbstractRenderer {
public final class PcfRenderer extends AbstractStringRenderer {
private final ValueFormatter valueFormatter;
private boolean isDocument;
@@ -227,7 +227,7 @@ public final class PcfRenderer extends AbstractRenderer {
}
private void visitStandaloneValue(Object value) {
if (value instanceof VmObjectLike && !VmUtils.isRenderDirective(value)) {
if (value instanceof VmObjectLike && !isRenderDirective(value)) {
builder.append("new ");
}
visit(value);

View File

@@ -36,7 +36,7 @@ import org.pkl.core.runtime.VmTyped;
import org.pkl.core.runtime.VmUtils;
import org.pkl.core.runtime.VmValue;
import org.pkl.core.runtime.VmValueConverter;
import org.pkl.core.stdlib.AbstractRenderer;
import org.pkl.core.stdlib.AbstractStringRenderer;
import org.pkl.core.stdlib.ExternalMethod1Node;
import org.pkl.core.stdlib.PklConverter;
import org.pkl.core.util.EconomicMaps;
@@ -74,7 +74,7 @@ public final class PropertiesRendererNodes {
return new PropertiesRenderer(builder, omitNullProperties, restrictCharset, PklConverter);
}
private static final class PropertiesRenderer extends AbstractRenderer {
private static final class PropertiesRenderer extends AbstractStringRenderer {
private final boolean restrictCharset;
private boolean isDocument;
@@ -158,7 +158,7 @@ public final class PropertiesRendererNodes {
.withProgramValue("Value", value)
.build();
}
if (!VmUtils.isRenderDirective(value)) {
if (!isRenderDirective(value)) {
isDocument = true;
}
visit(value);
@@ -170,7 +170,7 @@ public final class PropertiesRendererNodes {
|| value instanceof VmTyped
|| value instanceof VmMapping
|| value instanceof VmDynamic)
&& !VmUtils.isRenderDirective(value)) {
&& !isRenderDirective(value)) {
cannotRenderTypeAddConverter((VmValue) value);
}
isDocument = false;
@@ -303,7 +303,7 @@ public final class PropertiesRendererNodes {
if (isFollowing.get()) {
builder.append('.');
}
if (VmUtils.isRenderDirective(path)) {
if (isRenderDirective(path)) {
builder.append(VmUtils.readTextProperty(path));
} else {
builder.append(

View File

@@ -35,7 +35,7 @@ import org.pkl.core.runtime.VmRegex;
import org.pkl.core.runtime.VmSet;
import org.pkl.core.runtime.VmTyped;
import org.pkl.core.runtime.VmUtils;
import org.pkl.core.stdlib.AbstractRenderer;
import org.pkl.core.stdlib.AbstractStringRenderer;
import org.pkl.core.stdlib.ExternalMethod1Node;
import org.pkl.core.stdlib.PklConverter;
import org.pkl.core.util.MutableBoolean;
@@ -75,7 +75,7 @@ public final class YamlRendererNodes {
builder, " ".repeat(indentWidth), converter, omitNullProperties, mode, isStream);
}
private static final class YamlRenderer extends AbstractRenderer {
private static final class YamlRenderer extends AbstractStringRenderer {
private final boolean isStream;
private final YamlEmitter emitter;
private final String elementIndent;
@@ -306,7 +306,7 @@ public final class YamlRendererNodes {
return;
}
if (VmUtils.isRenderDirective(key)) {
if (isRenderDirective(key)) {
visitRenderDirective((VmTyped) key);
builder.append(':');
return;

View File

@@ -36,7 +36,7 @@ import org.pkl.core.runtime.VmRegex;
import org.pkl.core.runtime.VmSet;
import org.pkl.core.runtime.VmTyped;
import org.pkl.core.runtime.VmUtils;
import org.pkl.core.stdlib.AbstractRenderer;
import org.pkl.core.stdlib.AbstractStringRenderer;
import org.pkl.core.stdlib.ExternalMethod1Node;
import org.pkl.core.stdlib.PklConverter;
import org.pkl.core.util.ArrayCharEscaper;
@@ -72,7 +72,7 @@ public final class RendererNodes {
}
}
private static final class Renderer extends AbstractRenderer {
private static final class Renderer extends AbstractStringRenderer {
// Pattern for object fields that we can render without any quotes.
// From: https://jsonnet.org/ref/spec.html#lexing
private static final Pattern ID_PATTERN = Pattern.compile("[_a-zA-Z][_a-zA-Z0-9]*");
@@ -288,7 +288,7 @@ public final class RendererNodes {
builder.append(memberSeparator).append(currIndent);
if (key instanceof String string) {
renderAsFieldName(string);
} else if (VmUtils.isRenderDirective(key)) {
} else if (isRenderDirective(key)) {
visitRenderDirective((VmTyped) key);
builder.append(": ");
} else {

View File

@@ -0,0 +1,71 @@
/*
* Copyright © 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.
* 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.stdlib.pklbinary;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Specialization;
import org.msgpack.core.MessageBufferPacker;
import org.msgpack.core.MessagePack;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmBytes;
import org.pkl.core.runtime.VmMapping;
import org.pkl.core.runtime.VmPklBinaryEncoder;
import org.pkl.core.runtime.VmTyped;
import org.pkl.core.runtime.VmUtils;
import org.pkl.core.stdlib.ExternalMethod1Node;
import org.pkl.core.stdlib.PklConverter;
import org.pkl.core.util.Nullable;
public final class RendererNodes {
private abstract static class RenderMethod extends ExternalMethod1Node {
private @Nullable MessageBufferPacker messagePacker;
protected MessageBufferPacker getMessagePacker() {
if (messagePacker == null) {
messagePacker = MessagePack.newDefaultBufferPacker();
}
messagePacker.clear();
return messagePacker;
}
}
public abstract static class renderDocument extends RenderMethod {
@Specialization
@TruffleBoundary
protected VmBytes eval(VmTyped self, Object value) {
var packer = getMessagePacker();
createRenderer(self, packer).renderDocument(value);
return new VmBytes(packer.toByteArray());
}
}
public abstract static class renderValue extends RenderMethod {
@Specialization
@TruffleBoundary
protected VmBytes eval(VmTyped self, Object value) {
var packer = getMessagePacker();
createRenderer(self, packer).renderValue(value);
return new VmBytes(packer.toByteArray());
}
}
private static VmPklBinaryEncoder createRenderer(VmTyped self, MessageBufferPacker packer) {
var converters = (VmMapping) VmUtils.readMember(self, Identifier.CONVERTERS);
var converter = new PklConverter(converters);
return new VmPklBinaryEncoder(packer, converter);
}
}

View File

@@ -0,0 +1,4 @@
@NonnullByDefault
package org.pkl.core.stdlib.pklbinary;
import org.pkl.core.util.NonnullByDefault;

View File

@@ -63,7 +63,7 @@ import org.pkl.core.runtime.VmSet;
import org.pkl.core.runtime.VmTyped;
import org.pkl.core.runtime.VmUtils;
import org.pkl.core.runtime.VmValue;
import org.pkl.core.stdlib.AbstractRenderer;
import org.pkl.core.stdlib.AbstractStringRenderer;
import org.pkl.core.stdlib.ExternalMethod1Node;
import org.pkl.core.stdlib.PklConverter;
import org.pkl.core.util.ArrayCharEscaper;
@@ -140,7 +140,7 @@ public final class RendererNodes {
return new ProtobufRenderer(builder, indent, new PklConverter(converters));
}
private static final class ProtobufRenderer extends AbstractRenderer {
private static final class ProtobufRenderer extends AbstractStringRenderer {
private final Deque<Identifier> propertyPath = new ArrayDeque<>();
private final Deque<Boolean> wrapperRequirement = new ArrayDeque<>();
private final JsonEscaper jsonEscaper = new JsonEscaper(false);
@@ -306,7 +306,7 @@ public final class RendererNodes {
@Override
protected void visitEntryKey(Object key, boolean isFirst) {
var isDirective = VmUtils.isRenderDirective(key);
var isDirective = isRenderDirective(key);
var isValidKey =
isDirective || key instanceof Long || key instanceof Boolean || key instanceof String;
if (!isValidKey) {

View File

@@ -18,7 +18,7 @@ package org.pkl.core.stdlib.xml;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Specialization;
import org.pkl.core.runtime.*;
import org.pkl.core.stdlib.AbstractRenderer;
import org.pkl.core.stdlib.AbstractStringRenderer;
import org.pkl.core.stdlib.ExternalMethod1Node;
import org.pkl.core.stdlib.PklConverter;
import org.pkl.core.util.ArrayCharEscaper;
@@ -60,7 +60,7 @@ public final class RendererNodes {
}
}
public static final class Renderer extends AbstractRenderer {
public static final class Renderer extends AbstractStringRenderer {
// it's safe (though not required) to escape all the following characters in text nodes and
// attribute values
private static final ArrayCharEscaper stringEscaper =
@@ -266,7 +266,7 @@ public final class RendererNodes {
renderXmlInline((VmTyped) value);
} else if (isContent(value)) {
visit(value);
} else if (VmUtils.isRenderDirective(value)) {
} else if (isRenderDirective(value)) {
builder.append(VmUtils.readTextProperty(value));
} else {
writeXmlElement(VmUtils.getClass(value).getSimpleName(), null, value, true, true);
@@ -293,7 +293,7 @@ public final class RendererNodes {
} else {
assert deferredKey != null;
assert enclosingValue != null;
if (VmUtils.isRenderDirective(deferredKey)) {
if (isRenderDirective(deferredKey)) {
writeXmlElement(VmUtils.readTextProperty(deferredKey), null, value, true, false);
} else if (deferredKey instanceof String string) {
writeXmlElement(string, null, value, true, true);

View File

@@ -0,0 +1,468 @@
/*
* Copyright © 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.
* 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.pklbinary;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.Formatter;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
import org.msgpack.core.MessageInsufficientBufferException;
import org.msgpack.core.MessagePackException;
import org.msgpack.core.MessageUnpacker;
import org.pkl.core.DataSizeUnit;
import org.pkl.core.DurationUnit;
import org.pkl.core.Pair;
import org.pkl.core.util.LateInit;
/**
* Base class for implementing a decoder/parser for the <a
* href="https://pkl-lang.org/main/current/bindings-specification/binary-encoding.html"><code>
* pkl-binary</code></a> encoding.
*/
public abstract class AbstractPklBinaryDecoder {
private final MessageUnpacker unpacker;
@LateInit protected Deque<Object> currPath;
protected AbstractPklBinaryDecoder(MessageUnpacker unpacker) {
this.unpacker = unpacker;
}
protected static class DecodeException extends RuntimeException {
public DecodeException(String msg, Object... args) {
super(new Formatter().format(msg, args).toString());
}
}
protected final Object decode() {
currPath = new ArrayDeque<>();
try {
try {
return doDecode();
} catch (MessageInsufficientBufferException e) {
throw new DecodeException("Unexpected EOF", e);
}
} catch (IOException e) {
throw doIOFail(e);
} catch (MessagePackException | DecodeException e) {
var path = new ArrayList<String>(currPath.size());
for (var iter = currPath.descendingIterator(); iter.hasNext(); ) {
path.add(iter.next().toString());
}
Collections.reverse(path);
throw doFail(e, unpacker.getTotalReadBytes(), path);
}
}
private void assertLength(PklBinaryCode type, int len, int expected) {
if (len < expected) {
throw new DecodeException(
"Expected %s structure to have at least %d slots, found %d", type, expected + 1, len);
}
}
protected record DecodedObjectMember(PklBinaryCode type, Object key, Object value) {}
protected abstract RuntimeException doFail(Exception cause, long offset, List<String> path);
protected abstract RuntimeException doIOFail(IOException cause);
protected abstract Object doDecodeNull();
protected abstract Object doDecodeObject(
String className, URI moduleUri, DecodeIterator<DecodedObjectMember> iter);
protected abstract Object doDecodeMap(MapDecodeIterator iter);
protected abstract Object doDecodeMapping(MapDecodeIterator iter);
protected abstract Object doDecodeList(CollectionDecodeIterator iter);
protected abstract Object doDecodeListing(CollectionDecodeIterator iter);
protected abstract Object doDecodeSet(CollectionDecodeIterator iter);
protected abstract Object doDecodeDuration(double value, DurationUnit unit);
protected abstract Object doDecodeDataSize(double value, DataSizeUnit unit);
protected abstract Object doDecodePair(Object first, Object second);
protected abstract Object doDecodeIntSeq(long start, long end, long step);
protected abstract Object doDecodeRegex(Pattern pattern);
protected abstract Object doDecodeClass(String qualifiedName, URI moduleUri);
protected abstract Object doDecodeTypeAlias(String qualifiedName, URI moduleUri);
protected Object doDecodeFunction() {
throw new DecodeException("Cannot decode Function value");
}
protected abstract Object doDecodeBytes(byte[] bytes);
private Object doDecode() throws IOException {
if (!unpacker.hasNext()) {
throw new DecodeException("Unexpected EOF");
}
return switch (unpacker.getNextFormat().getValueType()) {
// primitives
case NIL -> {
unpacker.unpackNil();
yield doDecodeNull();
}
case STRING -> unpacker.unpackString();
case INTEGER -> unpacker.unpackLong();
case BOOLEAN -> unpacker.unpackBoolean();
case FLOAT -> unpacker.unpackDouble();
// non-primitive
case ARRAY -> decodeNonPrimitive();
// things we should never see outside a non-primitive
case BINARY -> throw new DecodeException("Unexpected msgpack bin value");
case MAP -> throw new DecodeException("Unexpected msgpack map value");
case EXTENSION -> throw new DecodeException("Unexpected msgpack ext value");
};
}
private Object decodeNonPrimitive() throws IOException {
var len = unpacker.unpackArrayHeader();
if (len < 1) {
throw new DecodeException("Unexpected empty object array value");
}
var codeInt = unpacker.unpackInt();
var code = PklBinaryCode.fromInt(codeInt);
if (code == null) {
throw new DecodeException("Unrecognized code 0x%x", (byte) codeInt);
}
return switch (code) {
case OBJECT -> decodeObject(len);
case MAP -> decodeMap(len);
case MAPPING -> decodeMapping(len);
case LIST -> decodeList(len);
case LISTING -> decodeListing(len);
case SET -> decodeSet(len);
case DURATION -> decodeDuration(len);
case DATASIZE -> decodeDataSize(len);
case PAIR -> decodePair(len);
case INTSEQ -> decodeIntSeq(len);
case REGEX -> decodeRegex(len);
case CLASS -> decodeClass(len);
case TYPEALIAS -> decodeTypeAlias(len);
case FUNCTION -> decodeFunction(len);
case BYTES -> decodeBytes(len);
default -> throw new DecodeException("Unrecognized object code %s", code);
};
}
private Object decodeObject(int len) throws IOException {
assertLength(PklBinaryCode.OBJECT, len, 3);
currPath.push("'object");
var className = unpacker.unpackString();
if (className.isBlank()) {
throw new DecodeException("Unexpected blank object class name");
}
var classModuleUriString = unpacker.unpackString();
if (classModuleUriString.isBlank()) {
throw new DecodeException("Unexpected blank object module URI");
}
var classModuleUri = URI.create(classModuleUriString);
var result =
doDecodeObject(
className, classModuleUri, new ObjectDecodeIterator(unpacker.unpackArrayHeader()));
unpacker.skipValue(len - 4);
currPath.pop();
return result;
}
private Object decodeMap(int len) throws IOException {
assertLength(PklBinaryCode.MAP, len, 1);
currPath.push("'map");
var result = doDecodeMap(new MapDecodeIterator(unpacker.unpackMapHeader()));
unpacker.skipValue(len - 2);
currPath.pop();
return result;
}
private Object decodeMapping(int len) throws IOException {
assertLength(PklBinaryCode.MAPPING, len, 1);
currPath.push("'mapping");
var result = doDecodeMapping(new MapDecodeIterator(unpacker.unpackMapHeader()));
unpacker.skipValue(len - 2);
currPath.pop();
return result;
}
private Object decodeList(int len) throws IOException {
assertLength(PklBinaryCode.LIST, len, 1);
currPath.push("'list");
var result = doDecodeList(new CollectionDecodeIterator(unpacker.unpackArrayHeader()));
unpacker.skipValue(len - 2);
currPath.pop();
return result;
}
private Object decodeListing(int len) throws IOException {
assertLength(PklBinaryCode.LISTING, len, 1);
currPath.push("'listing");
var result = doDecodeListing(new CollectionDecodeIterator(unpacker.unpackArrayHeader()));
unpacker.skipValue(len - 2);
currPath.pop();
return result;
}
private Object decodeSet(int len) throws IOException {
assertLength(PklBinaryCode.SET, len, 1);
currPath.push("'set");
var result = doDecodeSet(new CollectionDecodeIterator(unpacker.unpackArrayHeader()));
currPath.pop();
unpacker.skipValue(len - 2);
return result;
}
private Object decodeDuration(int len) throws IOException {
assertLength(PklBinaryCode.DURATION, len, 2);
currPath.push("'duration");
var durationValue = unpacker.unpackDouble();
var rawDurationUnit = unpacker.unpackString();
var durationUnit = DurationUnit.parse(rawDurationUnit);
if (durationUnit == null) {
throw new DecodeException("Invalid Duration unit `%s`", rawDurationUnit);
}
var result = doDecodeDuration(durationValue, durationUnit);
unpacker.skipValue(len - 3);
currPath.pop();
return result;
}
private Object decodeDataSize(int len) throws IOException {
assertLength(PklBinaryCode.DATASIZE, len, 2);
currPath.push("'datasize");
var dataSizeValue = unpacker.unpackDouble();
var rawDataSizeUnit = unpacker.unpackString();
var dataSizeUnit = DataSizeUnit.parse(rawDataSizeUnit);
if (dataSizeUnit == null) {
throw new DecodeException("Invalid DataSize unit `%s`", rawDataSizeUnit);
}
var result = doDecodeDataSize(dataSizeValue, dataSizeUnit);
unpacker.skipValue(len - 3);
currPath.pop();
return result;
}
private Object decodePair(int len) throws IOException {
assertLength(PklBinaryCode.PAIR, len, 2);
currPath.push("'pair");
currPath.push("'first");
var first = doDecode();
currPath.pop();
currPath.push("'second");
var second = doDecode();
currPath.pop();
var result = doDecodePair(first, second);
unpacker.skipValue(len - 3);
currPath.pop();
return result;
}
private Object decodeIntSeq(int len) throws IOException {
assertLength(PklBinaryCode.INTSEQ, len, 3);
currPath.push("'intseq");
var start = unpacker.unpackLong();
var end = unpacker.unpackLong();
var step = unpacker.unpackLong();
var result = doDecodeIntSeq(start, end, step);
unpacker.skipValue(len - 4);
currPath.pop();
return result;
}
private Object decodeRegex(int len) throws IOException {
assertLength(PklBinaryCode.REGEX, len, 1);
currPath.push("'regex");
var result = doDecodeRegex(Pattern.compile(unpacker.unpackString()));
unpacker.skipValue(len - 2);
currPath.pop();
return result;
}
private Object decodeClass(int len) throws IOException {
assertLength(PklBinaryCode.CLASS, len, 2);
currPath.push("'class");
var name = unpacker.unpackString();
if (name.isBlank()) {
throw new DecodeException("Unexpected blank class name");
}
var moduleUriString = unpacker.unpackString();
if (moduleUriString.isBlank()) {
throw new DecodeException("Unexpected blank class module URI");
}
var moduleUri = URI.create(moduleUriString);
var result = doDecodeClass(name, moduleUri);
unpacker.skipValue(len - 3);
currPath.pop();
return result;
}
private Object decodeTypeAlias(int len) throws IOException {
assertLength(PklBinaryCode.TYPEALIAS, len, 2);
currPath.push("'typealias");
var name = unpacker.unpackString();
if (name.isBlank()) {
throw new DecodeException("Unexpected blank typealias name");
}
var moduleUriString = unpacker.unpackString();
if (moduleUriString.isBlank()) {
throw new DecodeException("Unexpected blank typealias module URI");
}
var moduleUri = URI.create(moduleUriString);
var result = doDecodeTypeAlias(name, moduleUri);
unpacker.skipValue(len - 3);
currPath.pop();
return result;
}
private Object decodeFunction(int len) throws IOException {
assertLength(PklBinaryCode.FUNCTION, len, 0);
currPath.push("'function");
var result = doDecodeFunction();
unpacker.skipValue(len - 1);
currPath.pop();
return result;
}
private Object decodeBytes(int len) throws IOException {
assertLength(PklBinaryCode.BYTES, len, 1);
currPath.push("'bytes");
var result = doDecodeBytes(unpacker.readPayload(unpacker.unpackBinaryHeader()));
unpacker.skipValue(len - 2);
currPath.pop();
return result;
}
// some silly iterator classes because next() needs to handle IOException
protected abstract class DecodeIterator<T> implements Iterator<T> {
private final int size;
private int idx = 0;
DecodeIterator(int size) {
this.size = size;
}
public int getSize() {
return size;
}
public boolean hasNext() {
return idx < size;
}
public T next() {
currPath.push(idx);
try {
return getNext();
} catch (IOException e) {
throw doIOFail(e);
} finally {
currPath.pop();
idx++;
}
}
abstract T getNext() throws IOException;
}
protected class ObjectDecodeIterator extends DecodeIterator<DecodedObjectMember> {
ObjectDecodeIterator(int size) {
super(size);
}
@Override
DecodedObjectMember getNext() throws IOException {
var memberLen = unpacker.unpackArrayHeader();
if (memberLen != 3) {
throw new DecodeException("Expected 3 fields in object member, found %d", memberLen);
}
var memberCodeInt = unpacker.unpackInt();
var memberCode = PklBinaryCode.fromInt(memberCodeInt);
if (memberCode == null) {
throw new DecodeException("Unrecognized code 0x%x", (byte) memberCodeInt);
}
DecodedObjectMember member;
switch (memberCode) {
case PROPERTY -> {
var propertyName = unpacker.unpackString();
currPath.push(propertyName);
member = new DecodedObjectMember(memberCode, propertyName, doDecode());
}
case ENTRY -> {
var entryKey = doDecode();
currPath.push(entryKey);
member = new DecodedObjectMember(memberCode, entryKey, doDecode());
}
case ELEMENT -> {
var elementIndex = unpacker.unpackLong();
currPath.push(elementIndex);
member = new DecodedObjectMember(memberCode, elementIndex, doDecode());
}
default -> throw new DecodeException("Unrecognized member code %s", memberCode);
}
currPath.pop();
return member;
}
}
protected class CollectionDecodeIterator extends DecodeIterator<Object> {
CollectionDecodeIterator(int size) {
super(size);
}
@Override
Object getNext() throws IOException {
return doDecode();
}
}
protected class MapDecodeIterator extends DecodeIterator<Pair<Object, Object>> {
MapDecodeIterator(int size) {
super(size);
}
@Override
Pair<Object, Object> getNext() throws IOException {
var key = doDecode();
currPath.push(key);
var val = doDecode();
currPath.pop();
return new Pair<>(key, val);
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright © 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.
* 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.pklbinary;
import org.pkl.core.util.Nullable;
public enum PklBinaryCode {
OBJECT((byte) 0x01),
MAP((byte) 0x02),
MAPPING((byte) 0x03),
LIST((byte) 0x04),
LISTING((byte) 0x05),
SET((byte) 0x06),
DURATION((byte) 0x07),
DATASIZE((byte) 0x08),
PAIR((byte) 0x09),
INTSEQ((byte) 0x0A),
REGEX((byte) 0x0B),
CLASS((byte) 0x0C),
TYPEALIAS((byte) 0x0D),
FUNCTION((byte) 0x0E),
BYTES((byte) 0x0F),
PROPERTY((byte) 0x10),
ENTRY((byte) 0x11),
ELEMENT((byte) 0x12);
private final byte code;
PklBinaryCode(byte code) {
this.code = code;
}
public byte getCode() {
return code;
}
public static @Nullable PklBinaryCode fromInt(int value) {
return switch (value) {
case 0x01 -> PklBinaryCode.OBJECT;
case 0x02 -> PklBinaryCode.MAP;
case 0x03 -> PklBinaryCode.MAPPING;
case 0x04 -> PklBinaryCode.LIST;
case 0x05 -> PklBinaryCode.LISTING;
case 0x06 -> PklBinaryCode.SET;
case 0x07 -> PklBinaryCode.DURATION;
case 0x08 -> PklBinaryCode.DATASIZE;
case 0x09 -> PklBinaryCode.PAIR;
case 0x0A -> PklBinaryCode.INTSEQ;
case 0x0B -> PklBinaryCode.REGEX;
case 0x0C -> PklBinaryCode.CLASS;
case 0x0D -> PklBinaryCode.TYPEALIAS;
case 0x0E -> PklBinaryCode.FUNCTION;
case 0x0F -> PklBinaryCode.BYTES;
case 0x10 -> PklBinaryCode.PROPERTY;
case 0x11 -> PklBinaryCode.ENTRY;
case 0x12 -> PklBinaryCode.ELEMENT;
default -> null;
};
}
}

View File

@@ -0,0 +1,4 @@
@NonnullByDefault
package org.pkl.core.util.pklbinary;
import org.pkl.core.util.NonnullByDefault;

View File

@@ -0,0 +1,48 @@
open module encoding1
import "pkl:pklbinary"
import "pkl:base"
class Foo {
dynamic: Dynamic = new {
hello = "world"
["hello"] = "world"
"hello world"
}
string: String = "foo"
map = Map("foo", "bar")
mapping: Mapping = new { ["foo"] = "bar" }
list = List("foo", "bar")
listing: Listing = new { "foo"; 0 }
set = Set("foo", "bar")
duration = 123.h
dataSize = 123.gib
pair = Pair("foo", "bar")
intSeq = IntSeq(123, 456)
regex = Regex("foo.*")
func: ((String, Int) -> Boolean)? =
(a, b) -> a.sha256Int + b % 2 == 0
bytes = Bytes(0x01, 0x02, 0x03)
moduleClass: Class
baseModuleClass: Class = base.getClass()
pklbinaryModuleClass: Class = pklbinary.getClass()
stdlibClass: Class = PcfRenderer
someClass: Class = Foo
stdlibTypealias: TypeAlias = UInt
someTypealias: TypeAlias = Bar
something: Any = new PcfRenderer {} // a non-external class from pkl:base
}
typealias Bar = Mapping<String, String>
classInstance: Foo = new {
moduleClass = Map(true, module.getClass())[true]
}
hidden noFunc: Foo = (classInstance) { func = null }
hidden encoded: Bytes = new pklbinary.Renderer {}.renderValue(noFunc)
output {
bytes = encoded
}

View File

@@ -1,3 +1,5 @@
extends ".../pklbinaryTest.pkl"
res1 = "bar"
res2 = ""
res3 = 1

View File

@@ -1,5 +1,7 @@
module com.foo.bar.MyModule
extends ".../pklbinaryTest.pkl"
class Person {
firstName: String
lastName: String

View File

@@ -1,3 +1,5 @@
extends ".../pklbinaryTest.pkl"
res1: DataSize = 1.b
res2: DataSize = 2.kb
res3: DataSize = 3.kib

View File

@@ -1,3 +1,5 @@
extends ".../pklbinaryTest.pkl"
res1 = 1.ns
res2 = 2.us
res3 = 3.ms

View File

@@ -1,2 +1,4 @@
extends ".../pklbinaryTest.pkl"
res1 = IntSeq(1, 3)
res2 = IntSeq(1, 4).step(5)

View File

@@ -1,3 +1,5 @@
extends ".../pklbinaryTest.pkl"
res1: List<Int> = List(1, 3, 5, 7)
res2: Listing<Int> = new { 2; 4; 6; 8 }
res3: List<Int> = List()

View File

@@ -1,3 +1,5 @@
extends ".../pklbinaryTest.pkl"
res1: Map = Map("foo", 1, "bar", 2)
res2: Mapping = new {
["foo"] = 1

View File

@@ -1,2 +1,4 @@
extends ".../pklbinaryTest.pkl"
res1 = Pair(1, 2)
res2 = Pair("foo", "bar")

View File

@@ -1,3 +1,5 @@
extends ".../pklbinaryTest.pkl"
res1 = Regex("abc")
res2 = Regex("")
res3 = Regex("(?m)^abc$")

View File

@@ -1,3 +1,5 @@
extends ".../pklbinaryTest.pkl"
res1: Set<Int> = Set(1, 3, 5, 7)
res2: Set<Int> = Set()
res3: Set<Any> = Set(1, true, "", null)

View File

@@ -0,0 +1,13 @@
open module pklbinaryTest
import "pkl:pklbinary"
output {
// ensure no output file written for this specific module, only children that add properties
when (module.toMap().isEmpty) {
text = ""
} else {
value = if (module.toMap().isEmpty) "" else module
renderer = new pklbinary.Renderer {}
}
}

View File

@@ -8,10 +8,10 @@ at pkl.base#JsonRenderer.renderDocument (file:///$snippetsDir/input/api/jsonRend
Consider adding a converter to `output.converters`.
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -8,10 +8,10 @@ at pkl.jsonnet#Renderer.renderDocument (file:///$snippetsDir/input/api/jsonnetRe
Consider adding a converter to `output.converters`.
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -8,10 +8,10 @@ at pkl.base#PListRenderer.renderDocument (file:///$snippetsDir/input/api/pListRe
Consider adding a converter to `output.converters`.
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -8,10 +8,10 @@ at pkl.base#PcfRenderer.renderDocument (file:///$snippetsDir/input/api/pcfRender
Consider adding a converter to `output.converters`.
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -0,0 +1,190 @@
- 1
- 'encoding1#Foo'
- 'file:///$snippetsDir/input/api/pklbinary1.msgpack.yaml.pkl'
-
-
- 16
- 'dynamic'
-
- 1
- 'Dynamic'
- 'pkl:base'
-
-
- 16
- 'hello'
- 'world'
-
- 17
- 'hello'
- 'world'
-
- 18
- 0
- 'hello world'
-
- 16
- 'string'
- 'foo'
-
- 16
- 'map'
-
- 2
-
'foo': 'bar'
-
- 16
- 'mapping'
-
- 3
-
'foo': 'bar'
-
- 16
- 'list'
-
- 4
-
- 'foo'
- 'bar'
-
- 16
- 'listing'
-
- 5
-
- 'foo'
- 0
-
- 16
- 'set'
-
- 6
-
- 'foo'
- 'bar'
-
- 16
- 'duration'
-
- 7
- 123.0
- 'h'
-
- 16
- 'dataSize'
-
- 8
- 123.0
- 'gib'
-
- 16
- 'pair'
-
- 9
- 'foo'
- 'bar'
-
- 16
- 'intSeq'
-
- 10
- 123
- 456
- 1
-
- 16
- 'regex'
-
- 11
- 'foo.*'
-
- 16
- 'func'
- null
-
- 16
- 'bytes'
-
- 15
- !!binary 'AQID'
-
- 16
- 'moduleClass'
-
- 12
- 'encoding1'
- 'file:///$snippetsDir/input/api/pklbinary1.msgpack.yaml.pkl'
-
- 16
- 'baseModuleClass'
-
- 12
- 'ModuleClass'
- 'pkl:base'
-
- 16
- 'pklbinaryModuleClass'
-
- 12
- 'pkl.pklbinary'
- 'pkl:pklbinary'
-
- 16
- 'stdlibClass'
-
- 12
- 'PcfRenderer'
- 'pkl:base'
-
- 16
- 'someClass'
-
- 12
- 'encoding1#Foo'
- 'file:///$snippetsDir/input/api/pklbinary1.msgpack.yaml.pkl'
-
- 16
- 'stdlibTypealias'
-
- 13
- 'UInt'
- 'pkl:base'
-
- 16
- 'someTypealias'
-
- 13
- 'encoding1#Bar'
- 'file:///$snippetsDir/input/api/pklbinary1.msgpack.yaml.pkl'
-
- 16
- 'something'
-
- 1
- 'PcfRenderer'
- 'pkl:base'
-
-
- 16
- 'converters'
-
- 3
- {}
-
- 16
- 'extension'
- 'pcf'
-
- 16
- 'indent'
- ' '
-
- 16
- 'omitNullProperties'
- false
-
- 16
- 'useCustomStringDelimiters'
- false

View File

@@ -8,10 +8,10 @@ at propertiesRenderer10.properties#foo (file:///$snippetsDir/input/api/propertie
Consider adding a converter to `output.converters`.
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -8,10 +8,10 @@ at pkl.base#PropertiesRenderer.renderDocument (file:///$snippetsDir/input/api/pr
Consider adding a converter to `output.converters`.
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -8,7 +8,7 @@ res7 = "Cannot render value of type `DataSize` as Properties. Value: 1.mb"
res8 = "Cannot render value of type `List` as Properties. Value: List(\"pigeon\", \"parrot\")"
res9 = "Cannot render value of type `Set` as Properties. Value: Set(\"pigeon\", \"parrot\")"
res10 = "Cannot render value of type `Map` as Properties. Value: Map(\"name\", \"pigeon\", \"age\", 42)"
res11 = "Cannot render value of type `Listing` as Properties. Value: new Listing { ?; ? }"
res11 = "Cannot render value of type `Listing` as Properties. Value: new Listing { \"pigeon\"; \"parrot\" }"
res12 = "Cannot render value of type `Mapping` as Properties. Value: new Mapping { [\"name\"] = ?; [\"age\"] = ? }"
res13 = "Cannot render value of type `Dynamic` as Properties. Value: new Dynamic { name = ?; age = ? }"
res14 = "Cannot render value of type `propertiesRenderer4#Person` as Properties. Value: new Person { name = ?; age = ? }"

View File

@@ -8,10 +8,10 @@ at pkl.xml#Renderer.renderDocument (file:///$snippetsDir/input/api/xmlRenderer8.
Consider adding a converter to `output.converters`.
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -4,6 +4,6 @@
<name>Pigeon</name>
<age>42</age>
</pigeon2>
<res2>`xml.Element` is not supported here. Value: new Dynamic { _isXmlElement = true; name = ?; attributes = ?; isBlockFormat =...</res2>
<res3>`xml.Inline` is not supported here. Value: new Inline { value = ? }</res3>
<res2>`xml.Element` is not supported here. Value: new Dynamic { _isXmlElement = true; name = &quot;pigeon2&quot;; attributes {}; isBlockF...</res2>
<res3>`xml.Inline` is not supported here. Value: new Inline { value { name = ?; age = ? } }</res3>
</root>

View File

@@ -8,10 +8,10 @@ at pkl.base#YamlRenderer.renderDocument (file:///$snippetsDir/input/api/yamlRend
Consider adding a converter to `output.converters`.
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -5,10 +5,10 @@ x | bar = throw("Something went wrong")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at exceptions#foo.bar (file:///$snippetsDir/input/basic/exceptions.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -8,10 +8,10 @@ at class3#person (file:///$snippetsDir/input/classes/class3.pkl)
Did you mean any of the following?
address
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -10,10 +10,10 @@ xx | max = 3
^
at constraints5#res2.max (file:///$snippetsDir/input/classes/constraints5.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -8,10 +8,10 @@ at inheritanceError1#Derived (file:///$snippetsDir/input/classes/inheritanceErro
By default, classes are closed for extension.
To make a class extensible, add an `open` modifier: `open class MyClass { ... }`
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -10,10 +10,10 @@ Examples:
* `123` uses literal syntax to create an instance of class `Int`.
* `Pair(1, 2)` uses the `Pair()` constructor method to create an instance of class `Pair`.
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -8,10 +8,10 @@ at invalidInstantiation2#res1 (file:///$snippetsDir/input/classes/invalidInstant
Abstract classes cannot be instantiated.
Instead, instantiate a concrete subclass.
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -9,10 +9,10 @@ xx | a = "other"
^^^^^^^
at unionTypesErrorAlias#res1.a (file:///$snippetsDir/input/classes/unionTypesErrorAlias.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -14,10 +14,10 @@ x | a = List(1, 3.14, 2)
^^^^^^^^^^^^^^^^
at unionTypesErrorDifferent1#res1.a (file:///$snippetsDir/input/classes/unionTypesErrorDifferent1.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -14,10 +14,10 @@ x | a = List(1, 2, 3)
^^^^^^^^^^^^^
at unionTypesErrorDifferent2#res1.a (file:///$snippetsDir/input/classes/unionTypesErrorDifferent2.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -18,10 +18,10 @@ x | res1: Alias1|Alias2 = new Dynamic { }
^^^^^^^^^^^^^^^
at unionTypesErrorMultipleAliases#res1 (file:///$snippetsDir/input/classes/unionTypesErrorMultipleAliases.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -18,10 +18,10 @@ x | a = Map(
^^^^
at unionTypesErrorNested#res1.a (file:///$snippetsDir/input/classes/unionTypesErrorNested.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -10,10 +10,10 @@ x | a = 42
^^
at unionTypesErrorSimple#res1.a (file:///$snippetsDir/input/classes/unionTypesErrorSimple.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -9,10 +9,10 @@ x | a = "foox"
^^^^^^
at unionTypesErrorString1#res1.a (file:///$snippetsDir/input/classes/unionTypesErrorString1.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -9,10 +9,10 @@ xx | a = "other"
^^^^^^^
at unionTypesErrorString2#res1.a (file:///$snippetsDir/input/classes/unionTypesErrorString2.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -10,10 +10,10 @@ x | name = 42
^^
at wrongType1#pigeon.name (file:///$snippetsDir/input/classes/wrongType1.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -10,10 +10,10 @@ x | age = "42"
^^^^
at wrongType2#pigeon.age (file:///$snippetsDir/input/classes/wrongType2.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -10,10 +10,10 @@ xx | address = "Howdy St."
^^^^^^^^^^^
at wrongType3#person.address (file:///$snippetsDir/input/classes/wrongType3.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -10,10 +10,10 @@ xx | street = 4.gb
^^^^
at wrongType4#person.address.street (file:///$snippetsDir/input/classes/wrongType4.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -10,10 +10,10 @@ x | names: List<String>(!isEmpty)
^^^^^
at wrongType6#Person.names (file:///$snippetsDir/input/classes/wrongType6.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -9,10 +9,10 @@ x | res = analyze.importGraph(Set(reflect.Module(cannotFindModule).uri))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at analyzeImportsCannotFindModule#res (file:///$snippetsDir/input/errors/analyzeImportsCannotFindModule.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -9,10 +9,10 @@ x | res = analyze.importGraph(Set(reflect.Module(invalidGlob).uri))
^^^^^^^^^^^
at analyzeImportsInvalidGlob#res (file:///$snippetsDir/input/errors/analyzeImportsInvalidGlob.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -7,10 +7,10 @@ at analyzeInvalidModuleUri#result (file:///$snippetsDir/input/errors/analyzeInva
Illegal character in path at index 3: foo <>
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -5,10 +5,10 @@ x | result = analyze.importGraph(Set("foo.pkl"))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at analyzeRelativeModuleUri#result (file:///$snippetsDir/input/errors/analyzeRelativeModuleUri.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -2,12 +2,12 @@
The top-level value of a Pcf document must have type `Typed` or `Dynamic`, but got type `String`.
Value: "anyConverterError"
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
This value was converted during rendering. Previous: new ModuleClass {}. After: "anyConverterError".
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -5,10 +5,10 @@ xx | bird {
^^^^
at cannotAmendFixedProperty1#n (file:///$snippetsDir/input/errors/cannotAmendFixedProperty1.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -5,10 +5,10 @@ x | name = _name
^^^^
at cannotAssignFixedProperty1#p (file:///$snippetsDir/input/errors/cannotAssignFixedProperty1.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -9,10 +9,10 @@ x | ...new Dynamic { name = "Osprey" }
^^^^
at cannotAssignFixedProperty3#p.name (file:///$snippetsDir/input/errors/cannotAssignFixedProperty3.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -12,10 +12,10 @@ x | value: nothing = "foo"
^^^^^
at cannotAssignToNothing#value (file:///$snippetsDir/input/errors/cannotAssignToNothing.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -7,10 +7,10 @@ at cannotChangeFixed1#Dog (file:///$snippetsDir/input/errors/cannotChangeFixed1.
Property `name` must be declared fixed, because it overrides a fixed property on parent class `cannotChangeFixed1#Animal`.
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -7,10 +7,10 @@ at cannotChangeFixed2#Dog (file:///$snippetsDir/input/errors/cannotChangeFixed2.
Property `name` cannot be declared fixed, because it overrides a non-fixed property on parent class `cannotChangeFixed2#Animal`.
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -10,10 +10,10 @@ Did you mean any of the following?
"foyo"
"xfoo"
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -15,6 +15,7 @@ pkl:EvaluatorSettings
pkl:json
pkl:jsonnet
pkl:math
pkl:pklbinary
pkl:platform
pkl:Project
pkl:protobuf
@@ -31,10 +32,10 @@ x | res1 = nonExisting.bar
^^^^^^^^^^^
at cannotFindStdLibModule#res1 (file:///$snippetsDir/input/errors/cannotFindStdLibModule.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -8,10 +8,10 @@ at cannotInstantiateAbstractModule#res1 (file:///$snippetsDir/input/errors/canno
Abstract classes cannot be instantiated.
Instead, instantiate a concrete subclass.
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -8,10 +8,10 @@ at pkl.base#PcfRenderer.renderDocument (file:///$snippetsDir/input/errors/cannot
Consider adding a converter to `output.converters`.
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -9,10 +9,10 @@ To fix, do either of:
1. Add modifier `const` to method `f1`
2. Self-import this module, and reference this method from the import.
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -9,10 +9,10 @@ To fix, do either of:
1. Add modifier `const` to property `top`
2. Self-import this module, and reference this property from the import.
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -9,10 +9,10 @@ To fix, do either of:
1. Add modifier `const` to property `notConst`
2. Self-import this module, and reference this property from the import.
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

View File

@@ -9,10 +9,10 @@ To fix, do either of:
1. Add modifier `const` to property `a`
2. Self-import this module, and reference this property from the import.
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | bytes = text.encodeToBytes("UTF-8")
^^^^
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)

Some files were not shown because too many files have changed in this diff Show More