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

@@ -21,6 +21,7 @@
module pkl.base
import "pkl:jsonnet"
import "pkl:pklbinary"
import "pkl:protobuf"
import "pkl:xml"
@@ -112,12 +113,19 @@ abstract external class Module {
new xml.Renderer {}
else if (format == "yaml")
new YamlRenderer {}
else if (format == "pkl-binary")
new pklbinary.Renderer {}
else
throw(
"Unknown output format: `\(format)`. Supported formats are `json`, `jsonnet`, `pcf`, `plist`, `properties`, `textproto`, `xml`, `yaml`."
"Unknown output format: `\(format)`. Supported formats are `json`, `jsonnet`, `pcf`, `plist`, `properties`, `textproto`, `xml`, `yaml`, `pkl-binary`."
)
text = renderer.renderDocument(value)
bytes = text.encodeToBytes("UTF-8")
text =
if (renderer is ValueRenderer)
renderer.renderDocument(value)
else
throw("Unable to render text when renderer is a BytesRenderer")
bytes =
if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
}
}
@@ -240,16 +248,21 @@ open class FileOutput {
value: Any?
/// The renderer for [value].
renderer: ValueRenderer = new PcfRenderer {}
renderer: BaseValueRenderer = new PcfRenderer {}
/// The textual rendered output.
text: String = renderer.renderDocument(value)
text: String =
if (renderer is ValueRenderer)
renderer.renderDocument(value)
else
throw("unabled to render text when renderer is a BytesRenderer")
/// The underlying byte output.
///
/// If unset, defaults to the UTF-8 encoding of [text].
@Since { version = "0.29.0" }
bytes: Bytes = text.encodeToBytes("UTF-8")
bytes: Bytes =
if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
}
/// The contents and appearance of a module's output.
@@ -284,9 +297,8 @@ class ModuleOutput extends FileOutput {
}
/// Base class for rendering Pkl values in some output format.
///
/// A renderer's output is guaranteed to be well-formed unless [RenderDirective] is part of the input.
abstract class ValueRenderer {
@Since { version = "0.30.0" }
abstract class BaseValueRenderer {
/// Value converters to apply before values are rendered.
///
/// A value converter is a function that converts a value to another value
@@ -334,7 +346,12 @@ abstract class ValueRenderer {
/// The file extension associated with this output format,
/// or [null] if this format does not have an extension.
extension: String?
}
/// Base class for rendering Pkl values in some textual output format.
///
/// A renderer's output is guaranteed to be well-formed unless [RenderDirective] is part of the input.
abstract class ValueRenderer extends BaseValueRenderer {
/// Renders [value] as a complete document.
///
/// Some renderers impose restrictions on which types of values can be rendered as document.
@@ -347,6 +364,23 @@ abstract class ValueRenderer {
abstract function renderValue(value: Any): String
}
/// Base class for rendering Pkl values in some binary output format.
///
/// A renderer's output is guaranteed to be well-formed unless [RenderDirective] is part of the input.
@Since { version = "0.30.0" }
abstract class BytesRenderer extends BaseValueRenderer {
/// Renders [value] as a complete document.
///
/// Some renderers impose restrictions on which types of values can be rendered as document.
///
/// A typical implementation of this method renders a document header/footer
/// and otherwise delegates to [renderValue()].
abstract function renderDocument(value: Any): Bytes
/// Renders [value].
abstract function renderValue(value: Any): Bytes
}
/// Renders values as Pcf, a static subset of Pkl.
class PcfRenderer extends ValueRenderer {
extension = "pcf"
@@ -401,6 +435,12 @@ class PcfRenderer extends ValueRenderer {
class RenderDirective {
/// The text to output as-is (without escaping or quoting).
text: String
/// The bytes to output as-is (without escaping or quoting).
///
/// Only used by [BytesRenderer], ignored by [ValueRenderer].
@Since { version = "0.30.0" }
bytes: Bytes = text.encodeToBytes("UTF-8")
}
/// Directs [PcfRenderer] to output additional text [before] and/or [after] rendering a [value].

28
stdlib/pklbinary.pkl Normal file
View File

@@ -0,0 +1,28 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
/// APIs for handling Pkl's [binary encoding format](https://pkl-lang.org/main/current/bindings-specification/binary-encoding.html).
@ModuleInfo { minPklVersion = "0.30.0" }
module pkl.pklbinary
/// Render values as the [`pkl-binary` encoding format](https://pkl-lang.org/main/current/bindings-specification/binary-encoding.html).
class Renderer extends BytesRenderer {
/// Render a Pkl value as `pkl-binary`.
external function renderValue(value: Any): Bytes
/// Render a Pkl document as `pkl-binary`.
external function renderDocument(value: Any): Bytes
}