mirror of
https://github.com/apple/pkl.git
synced 2026-07-10 23:22:46 +02:00
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:
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@NonnullByDefault
|
||||
package org.pkl.core.stdlib.pklbinary;
|
||||
|
||||
import org.pkl.core.util.NonnullByDefault;
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@NonnullByDefault
|
||||
package org.pkl.core.util.pklbinary;
|
||||
|
||||
import org.pkl.core.util.NonnullByDefault;
|
||||
Reference in New Issue
Block a user