mirror of
https://github.com/apple/pkl.git
synced 2026-05-23 23:37:13 +02:00
Move pkl-formatter to Java (#1514)
This commit is contained in:
@@ -24,22 +24,21 @@ org.jetbrains.kotlin:kotlin-daemon-client:2.2.21=kotlinBuildToolsApiClasspath
|
|||||||
org.jetbrains.kotlin:kotlin-daemon-embeddable:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable
|
org.jetbrains.kotlin:kotlin-daemon-embeddable:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable
|
||||||
org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:2.2.21=kotlinKlibCommonizerClasspath
|
org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:2.2.21=kotlinKlibCommonizerClasspath
|
||||||
org.jetbrains.kotlin:kotlin-metadata-jvm:2.2.21=kotlinInternalAbiValidation
|
org.jetbrains.kotlin:kotlin-metadata-jvm:2.2.21=kotlinInternalAbiValidation
|
||||||
org.jetbrains.kotlin:kotlin-native-prebuilt:2.0.21=kotlinNativeBundleConfiguration
|
|
||||||
org.jetbrains.kotlin:kotlin-reflect:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable
|
org.jetbrains.kotlin:kotlin-reflect:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable
|
||||||
org.jetbrains.kotlin:kotlin-script-runtime:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable
|
org.jetbrains.kotlin:kotlin-script-runtime:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable
|
||||||
org.jetbrains.kotlin:kotlin-scripting-common:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
org.jetbrains.kotlin:kotlin-scripting-common:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||||
org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||||
org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||||
org.jetbrains.kotlin:kotlin-scripting-jvm:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
org.jetbrains.kotlin:kotlin-scripting-jvm:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||||
org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.2.21=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.2.21=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.21=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.21=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-stdlib:2.2.21=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,runtimeClasspath,swiftExportClasspathResolvable,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
org.jetbrains.kotlin:kotlin-stdlib:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||||
org.jetbrains.kotlin:swift-export-embeddable:2.2.21=swiftExportClasspathResolvable
|
org.jetbrains.kotlin:swift-export-embeddable:2.2.21=swiftExportClasspathResolvable
|
||||||
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable
|
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable
|
||||||
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.7.3=swiftExportClasspathResolvable
|
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.7.3=swiftExportClasspathResolvable
|
||||||
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.7.3=swiftExportClasspathResolvable
|
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.7.3=swiftExportClasspathResolvable
|
||||||
org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3=swiftExportClasspathResolvable
|
org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3=swiftExportClasspathResolvable
|
||||||
org.jetbrains:annotations:13.0=compileClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,runtimeClasspath,swiftExportClasspathResolvable,testCompileClasspath,testRuntimeClasspath
|
org.jetbrains:annotations:13.0=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable,testCompileClasspath,testRuntimeClasspath
|
||||||
org.jspecify:jspecify:1.0.0=testCompileClasspath,testImplementationDependenciesMetadata
|
org.jspecify:jspecify:1.0.0=testCompileClasspath,testImplementationDependenciesMetadata
|
||||||
org.junit.jupiter:junit-jupiter-api:6.0.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
org.junit.jupiter:junit-jupiter-api:6.0.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||||
org.junit.jupiter:junit-jupiter-engine:6.0.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
org.junit.jupiter:junit-jupiter-engine:6.0.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||||
@@ -50,4 +49,4 @@ org.junit.platform:junit-platform-launcher:6.0.3=testRuntimeClasspath
|
|||||||
org.junit:junit-bom:6.0.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
org.junit:junit-bom:6.0.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||||
org.msgpack:msgpack-core:0.9.11=testRuntimeClasspath
|
org.msgpack:msgpack-core:0.9.11=testRuntimeClasspath
|
||||||
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||||
empty=annotationProcessor,compileOnlyDependenciesMetadata,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDefExtensions,signatures,sourcesJar,testAnnotationProcessor,testApiDependenciesMetadata,testCompileOnlyDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDefExtensions
|
empty=annotationProcessor,apiDependenciesMetadata,compileClasspath,compileOnlyDependenciesMetadata,implementationDependenciesMetadata,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDefExtensions,runtimeClasspath,sourcesJar,testAnnotationProcessor,testApiDependenciesMetadata,testCompileOnlyDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDefExtensions
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
* Copyright © 2025-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
plugins {
|
plugins {
|
||||||
pklAllProjects
|
pklAllProjects
|
||||||
pklKotlinLibrary
|
pklJavaLibrary
|
||||||
pklPublishLibrary
|
pklPublishLibrary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 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.formatter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
sealed interface FormatNode
|
||||||
|
permits Text,
|
||||||
|
Empty,
|
||||||
|
Line,
|
||||||
|
ForceLine,
|
||||||
|
SpaceOrLine,
|
||||||
|
Space,
|
||||||
|
Indent,
|
||||||
|
Nodes,
|
||||||
|
Group,
|
||||||
|
MultilineStringGroup,
|
||||||
|
IfWrap {
|
||||||
|
|
||||||
|
default int width(Set<Integer> wrapped) {
|
||||||
|
if (this instanceof Nodes n) {
|
||||||
|
return n.nodes().stream().mapToInt(node -> node.width(wrapped)).sum();
|
||||||
|
} else if (this instanceof Group g) {
|
||||||
|
return g.nodes().stream().mapToInt(node -> node.width(wrapped)).sum();
|
||||||
|
} else if (this instanceof Indent i) {
|
||||||
|
return i.nodes().stream().mapToInt(node -> node.width(wrapped)).sum();
|
||||||
|
} else if (this instanceof IfWrap iw) {
|
||||||
|
return wrapped.contains(iw.id()) ? iw.ifWrap().width(wrapped) : iw.ifNotWrap().width(wrapped);
|
||||||
|
} else if (this instanceof Text t) {
|
||||||
|
return t.text().length();
|
||||||
|
} else if (this instanceof SpaceOrLine || this instanceof Space) {
|
||||||
|
return 1;
|
||||||
|
} else if (this instanceof ForceLine || this instanceof MultilineStringGroup) {
|
||||||
|
return Generator.MAX;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record Text(String text) implements FormatNode {}
|
||||||
|
|
||||||
|
record Empty() implements FormatNode {
|
||||||
|
static final Empty INSTANCE = new Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
record Line() implements FormatNode {
|
||||||
|
static final Line INSTANCE = new Line();
|
||||||
|
}
|
||||||
|
|
||||||
|
record ForceLine() implements FormatNode {
|
||||||
|
static final ForceLine INSTANCE = new ForceLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
record SpaceOrLine() implements FormatNode {
|
||||||
|
static final SpaceOrLine INSTANCE = new SpaceOrLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
record Space() implements FormatNode {
|
||||||
|
static final Space INSTANCE = new Space();
|
||||||
|
}
|
||||||
|
|
||||||
|
record Indent(List<FormatNode> nodes) implements FormatNode {}
|
||||||
|
|
||||||
|
record Nodes(List<FormatNode> nodes) implements FormatNode {}
|
||||||
|
|
||||||
|
record Group(int id, List<FormatNode> nodes) implements FormatNode {}
|
||||||
|
|
||||||
|
record MultilineStringGroup(int endQuoteCol, List<FormatNode> nodes) implements FormatNode {}
|
||||||
|
|
||||||
|
record IfWrap(int id, FormatNode ifWrap, FormatNode ifNotWrap) implements FormatNode {}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 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.formatter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import org.pkl.parser.GenericParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A formatter for Pkl files that applies canonical formatting rules.
|
||||||
|
*
|
||||||
|
* @see GrammarVersion
|
||||||
|
*/
|
||||||
|
public class Formatter {
|
||||||
|
|
||||||
|
private final GrammarVersion grammarVersion;
|
||||||
|
|
||||||
|
public Formatter(GrammarVersion grammarVersion) {
|
||||||
|
this.grammarVersion = grammarVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Formatter() {
|
||||||
|
this(GrammarVersion.latest());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a Pkl file from the given file path.
|
||||||
|
*
|
||||||
|
* @param path the path to the Pkl file to format
|
||||||
|
* @param grammarVersion grammar compatibility version
|
||||||
|
* @return the formatted Pkl source code as a string
|
||||||
|
* @throws java.io.IOException if the file cannot be read
|
||||||
|
* @deprecated use {@code format(Files.readString(path))} instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public String format(Path path, GrammarVersion grammarVersion) throws IOException {
|
||||||
|
return new Formatter(grammarVersion).format(Files.readString(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a Pkl file from the given file path.
|
||||||
|
*
|
||||||
|
* @param path the path to the Pkl file to format
|
||||||
|
* @return the formatted Pkl source code as a string
|
||||||
|
* @throws java.io.IOException if the file cannot be read
|
||||||
|
* @deprecated use {@code format(Files.readString(path))} instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public String format(Path path) throws IOException {
|
||||||
|
return format(path, GrammarVersion.latest());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the given Pkl source code text.
|
||||||
|
*
|
||||||
|
* @param text the Pkl source code to format
|
||||||
|
* @param grammarVersion grammar compatibility version
|
||||||
|
* @return the formatted Pkl source code as a string
|
||||||
|
* @deprecated use {@code new Formatter(grammarVersion).format(text)} instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public String format(String text, GrammarVersion grammarVersion) {
|
||||||
|
return new Formatter(grammarVersion).format(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the given Pkl source code text.
|
||||||
|
*
|
||||||
|
* @param text the Pkl source code to format
|
||||||
|
* @return the formatted Pkl source code as a string
|
||||||
|
*/
|
||||||
|
public String format(String text) {
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
format(text, sb);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the given Pkl source code text.
|
||||||
|
*
|
||||||
|
* <p>It is the caller's responsibility to close {@code input}, and, if applicable, {@code
|
||||||
|
* output}.
|
||||||
|
*
|
||||||
|
* @param input the Pkl source code to format
|
||||||
|
* @param output the formatted Pkl source code
|
||||||
|
* @throws IOException if an I/O error occurs during reading or writing
|
||||||
|
*/
|
||||||
|
public void format(Reader input, Appendable output) throws IOException {
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
var buf = new char[8192];
|
||||||
|
int n;
|
||||||
|
while ((n = input.read(buf)) != -1) {
|
||||||
|
sb.append(buf, 0, n);
|
||||||
|
}
|
||||||
|
format(sb.toString(), output);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void format(String input, Appendable output) {
|
||||||
|
var ast = new GenericParser().parseModule(input);
|
||||||
|
var formatAst = new Builder(input, grammarVersion).format(ast);
|
||||||
|
// force a line at the end of the file
|
||||||
|
var nodes = new Nodes(List.of(formatAst, ForceLine.INSTANCE));
|
||||||
|
new Generator(output).generate(nodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 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.formatter;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
final class Generator {
|
||||||
|
|
||||||
|
static final int MAX = 100;
|
||||||
|
private static final String INDENT = " ";
|
||||||
|
|
||||||
|
private final Appendable buf;
|
||||||
|
private int indent = 0;
|
||||||
|
private int size = 0;
|
||||||
|
private final Set<Integer> wrapped = new HashSet<>();
|
||||||
|
private boolean shouldAddIndent = false;
|
||||||
|
|
||||||
|
Generator(Appendable buf) {
|
||||||
|
this.buf = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void generate(FormatNode node) {
|
||||||
|
node(node, Wrap.DETECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("StatementWithEmptyBody")
|
||||||
|
private void node(FormatNode node, Wrap wrap) {
|
||||||
|
if (node instanceof Empty) {
|
||||||
|
// nothing
|
||||||
|
} else if (node instanceof Nodes n) {
|
||||||
|
for (var child : n.nodes()) {
|
||||||
|
node(child, wrap);
|
||||||
|
}
|
||||||
|
} else if (node instanceof Group g) {
|
||||||
|
var width = 0;
|
||||||
|
for (var child : g.nodes()) {
|
||||||
|
width += child.width(wrapped);
|
||||||
|
}
|
||||||
|
var groupWrap = wrap;
|
||||||
|
if (size + width > MAX) {
|
||||||
|
wrapped.add(g.id());
|
||||||
|
groupWrap = Wrap.ENABLED;
|
||||||
|
} else {
|
||||||
|
groupWrap = Wrap.DETECT;
|
||||||
|
}
|
||||||
|
for (var child : g.nodes()) {
|
||||||
|
node(child, groupWrap);
|
||||||
|
}
|
||||||
|
} else if (node instanceof IfWrap iw) {
|
||||||
|
if (wrapped.contains(iw.id())) {
|
||||||
|
node(iw.ifWrap(), Wrap.ENABLED);
|
||||||
|
} else {
|
||||||
|
node(iw.ifNotWrap(), wrap);
|
||||||
|
}
|
||||||
|
} else if (node instanceof Text t) {
|
||||||
|
text(t.text());
|
||||||
|
} else if (node instanceof Line) {
|
||||||
|
if (wrap.isEnabled()) {
|
||||||
|
newline(true);
|
||||||
|
}
|
||||||
|
} else if (node instanceof ForceLine) {
|
||||||
|
newline(true);
|
||||||
|
} else if (node instanceof SpaceOrLine) {
|
||||||
|
if (wrap.isEnabled()) {
|
||||||
|
newline(true);
|
||||||
|
} else {
|
||||||
|
text(" ");
|
||||||
|
}
|
||||||
|
} else if (node instanceof Space) {
|
||||||
|
text(" ");
|
||||||
|
} else if (node instanceof Indent ind) {
|
||||||
|
if (wrap.isEnabled() && !ind.nodes().isEmpty()) {
|
||||||
|
size += INDENT.length();
|
||||||
|
indent++;
|
||||||
|
for (var child : ind.nodes()) {
|
||||||
|
node(child, wrap);
|
||||||
|
}
|
||||||
|
indent--;
|
||||||
|
} else {
|
||||||
|
for (var child : ind.nodes()) {
|
||||||
|
node(child, wrap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (node instanceof MultilineStringGroup multi) {
|
||||||
|
var indentLength = indent * INDENT.length();
|
||||||
|
var oldIndent = indentFor(multi);
|
||||||
|
var previousNewline = false;
|
||||||
|
var nodes = multi.nodes();
|
||||||
|
for (var i = 0; i < nodes.size(); i++) {
|
||||||
|
var child = nodes.get(i);
|
||||||
|
if (child instanceof ForceLine) {
|
||||||
|
newline(false);
|
||||||
|
} else if (child instanceof Text t
|
||||||
|
&& previousNewline
|
||||||
|
&& t.text().isBlank()
|
||||||
|
&& t.text().length() == oldIndent
|
||||||
|
&& nodes.get(i + 1) instanceof ForceLine) {
|
||||||
|
// skip blank line indentation that will be repositioned
|
||||||
|
} else if (child instanceof Text t && previousNewline) {
|
||||||
|
text(reposition(t.text(), multi.endQuoteCol() - 1, indentLength));
|
||||||
|
} else {
|
||||||
|
node(child, Wrap.DETECT);
|
||||||
|
}
|
||||||
|
previousNewline = child instanceof ForceLine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void text(String value) {
|
||||||
|
try {
|
||||||
|
if (shouldAddIndent) {
|
||||||
|
for (var i = 0; i < indent; i++) {
|
||||||
|
buf.append(INDENT);
|
||||||
|
}
|
||||||
|
shouldAddIndent = false;
|
||||||
|
}
|
||||||
|
size += value.length();
|
||||||
|
buf.append(value);
|
||||||
|
} catch (java.io.IOException e) {
|
||||||
|
throw new java.io.UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void newline(boolean shouldIndent) {
|
||||||
|
try {
|
||||||
|
size = INDENT.length() * indent;
|
||||||
|
buf.append('\n');
|
||||||
|
shouldAddIndent = shouldIndent;
|
||||||
|
} catch (java.io.IOException e) {
|
||||||
|
throw new java.io.UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// accept text indented by originalOffset characters (tabs or spaces)
|
||||||
|
// and return it indented by newOffset characters (spaces only)
|
||||||
|
private static String reposition(String text, int originalOffset, int newOffset) {
|
||||||
|
return " ".repeat(newOffset) + text.substring(originalOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int indentFor(MultilineStringGroup multi) {
|
||||||
|
var nodes = multi.nodes();
|
||||||
|
if (nodes.size() < 2) return 0;
|
||||||
|
var beforeLast = nodes.get(nodes.size() - 2);
|
||||||
|
return beforeLast instanceof Text t ? t.text().length() : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 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.formatter;
|
||||||
|
|
||||||
|
/** Grammar compatibility version. */
|
||||||
|
public enum GrammarVersion {
|
||||||
|
V1(1, "0.25 - 0.29"),
|
||||||
|
V2(2, "0.30+");
|
||||||
|
|
||||||
|
private final int version;
|
||||||
|
private final String versionSpan;
|
||||||
|
|
||||||
|
GrammarVersion(int version, String versionSpan) {
|
||||||
|
this.version = version;
|
||||||
|
this.versionSpan = versionSpan;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVersionSpan() {
|
||||||
|
return versionSpan;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GrammarVersion latest() {
|
||||||
|
var latest = V1;
|
||||||
|
for (var v : values()) {
|
||||||
|
if (v.version > latest.version) {
|
||||||
|
latest = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return latest;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 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.formatter;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
final class NaturalOrderComparator implements Comparator<String> {
|
||||||
|
|
||||||
|
private final boolean ignoreCase;
|
||||||
|
|
||||||
|
NaturalOrderComparator(boolean ignoreCase) {
|
||||||
|
this.ignoreCase = ignoreCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
NaturalOrderComparator() {
|
||||||
|
this(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(String s1, String s2) {
|
||||||
|
var i = 0;
|
||||||
|
var j = 0;
|
||||||
|
|
||||||
|
while (i < s1.length() && j < s2.length()) {
|
||||||
|
var c1 = ignoreCase ? Character.toLowerCase(s1.charAt(i)) : s1.charAt(i);
|
||||||
|
var c2 = ignoreCase ? Character.toLowerCase(s2.charAt(j)) : s2.charAt(j);
|
||||||
|
|
||||||
|
if (Character.isDigit(c1) && Character.isDigit(c2)) {
|
||||||
|
var pair1 = getNumber(s1, i);
|
||||||
|
var pair2 = getNumber(s2, j);
|
||||||
|
|
||||||
|
var numComparison = Long.compare(pair1.l, pair2.l);
|
||||||
|
if (numComparison != 0) {
|
||||||
|
return numComparison;
|
||||||
|
}
|
||||||
|
i = pair1.i;
|
||||||
|
j = pair2.i;
|
||||||
|
} else {
|
||||||
|
var charComparison = Character.compare(c1, c2);
|
||||||
|
if (charComparison != 0) {
|
||||||
|
return charComparison;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Integer.compare(s1.length(), s2.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LongAndInt getNumber(String s, int startIndex) {
|
||||||
|
var i = startIndex;
|
||||||
|
while (i < s.length() && Character.isDigit(s.charAt(i))) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var number = Long.parseLong(s, startIndex, i, 10);
|
||||||
|
return new LongAndInt(number, i);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return new LongAndInt(0L, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record LongAndInt(long l, int i) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 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.formatter;
|
||||||
|
|
||||||
|
enum Wrap {
|
||||||
|
ENABLED,
|
||||||
|
DETECT;
|
||||||
|
|
||||||
|
boolean isEnabled() {
|
||||||
|
return this == ENABLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,103 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2025-2026 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.formatter
|
|
||||||
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.Reader
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.jvm.Throws
|
|
||||||
import org.pkl.formatter.ast.ForceLine
|
|
||||||
import org.pkl.formatter.ast.Nodes
|
|
||||||
import org.pkl.parser.GenericParser
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A formatter for Pkl files that applies canonical formatting rules.
|
|
||||||
*
|
|
||||||
* @param grammarVersion grammar compatibility version
|
|
||||||
*/
|
|
||||||
class Formatter
|
|
||||||
@JvmOverloads
|
|
||||||
constructor(private val grammarVersion: GrammarVersion = GrammarVersion.latest()) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formats a Pkl file from the given file path.
|
|
||||||
*
|
|
||||||
* @param path the path to the Pkl file to format
|
|
||||||
* @param grammarVersion grammar compatibility version
|
|
||||||
* @return the formatted Pkl source code as a string
|
|
||||||
* @throws java.io.IOException if the file cannot be read
|
|
||||||
*/
|
|
||||||
@JvmOverloads
|
|
||||||
@Deprecated(message = "use format(path.readText()) instead")
|
|
||||||
fun format(path: Path, grammarVersion: GrammarVersion = GrammarVersion.latest()): String {
|
|
||||||
return Formatter(grammarVersion).format(Files.readString(path))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formats the given Pkl source code text.
|
|
||||||
*
|
|
||||||
* @param text the Pkl source code to format
|
|
||||||
* @param grammarVersion grammar compatibility version
|
|
||||||
* @return the formatted Pkl source code as a string
|
|
||||||
*/
|
|
||||||
@Deprecated(message = "use Formatter(grammarVersion).format(text) instead")
|
|
||||||
fun format(text: String, grammarVersion: GrammarVersion): String {
|
|
||||||
return Formatter(grammarVersion).format(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formats the given Pkl source code text.
|
|
||||||
*
|
|
||||||
* @param text the Pkl source code to format
|
|
||||||
* @return the formatted Pkl source code as a string
|
|
||||||
*/
|
|
||||||
fun format(text: String): String {
|
|
||||||
return buildString { format(text, this) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formats the given Pkl source code text.
|
|
||||||
*
|
|
||||||
* It is the caller's responsibility to close [input], and, if applicable, [output].
|
|
||||||
*
|
|
||||||
* @param input the Pkl source code to format
|
|
||||||
* @param output the formatted Pkl source code
|
|
||||||
* @throws java.io.IOException if an I/O error occurs during reading or writing
|
|
||||||
*/
|
|
||||||
@Throws(IOException::class)
|
|
||||||
fun format(input: Reader, output: Appendable) {
|
|
||||||
format(input.readText(), output)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun format(input: String, output: Appendable) {
|
|
||||||
val ast = GenericParser().parseModule(input)
|
|
||||||
val formatAst = Builder(input, grammarVersion).format(ast)
|
|
||||||
// force a line at the end of the file
|
|
||||||
val nodes = Nodes(listOf(formatAst, ForceLine))
|
|
||||||
Generator(output).generate(nodes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Grammar compatibility version. */
|
|
||||||
enum class GrammarVersion(val version: Int, val versionSpan: String) {
|
|
||||||
V1(1, "0.25 - 0.29"),
|
|
||||||
V2(2, "0.30+");
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmStatic fun latest(): GrammarVersion = entries.maxBy { it.version }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2025-2026 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.formatter
|
|
||||||
|
|
||||||
import org.pkl.formatter.ast.Empty
|
|
||||||
import org.pkl.formatter.ast.ForceLine
|
|
||||||
import org.pkl.formatter.ast.FormatNode
|
|
||||||
import org.pkl.formatter.ast.Group
|
|
||||||
import org.pkl.formatter.ast.IfWrap
|
|
||||||
import org.pkl.formatter.ast.Indent
|
|
||||||
import org.pkl.formatter.ast.Line
|
|
||||||
import org.pkl.formatter.ast.MultilineStringGroup
|
|
||||||
import org.pkl.formatter.ast.Nodes
|
|
||||||
import org.pkl.formatter.ast.Space
|
|
||||||
import org.pkl.formatter.ast.SpaceOrLine
|
|
||||||
import org.pkl.formatter.ast.Text
|
|
||||||
import org.pkl.formatter.ast.Wrap
|
|
||||||
|
|
||||||
internal class Generator(private val buf: Appendable) {
|
|
||||||
private var indent: Int = 0
|
|
||||||
private var size: Int = 0
|
|
||||||
private val wrapped: MutableSet<Int> = mutableSetOf()
|
|
||||||
private var shouldAddIndent = false
|
|
||||||
|
|
||||||
fun generate(node: FormatNode) {
|
|
||||||
node(node, Wrap.DETECT)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun node(node: FormatNode, wrap: Wrap) {
|
|
||||||
when (node) {
|
|
||||||
is Empty -> {}
|
|
||||||
is Nodes -> node.nodes.forEach { node(it, wrap) }
|
|
||||||
is Group -> {
|
|
||||||
val width = node.nodes.sumOf { it.width(wrapped) }
|
|
||||||
val wrap =
|
|
||||||
if (size + width > MAX) {
|
|
||||||
wrapped += node.id
|
|
||||||
Wrap.ENABLED
|
|
||||||
} else {
|
|
||||||
Wrap.DETECT
|
|
||||||
}
|
|
||||||
node.nodes.forEach { node(it, wrap) }
|
|
||||||
}
|
|
||||||
is IfWrap -> {
|
|
||||||
if (wrapped.contains(node.id)) {
|
|
||||||
node(node.ifWrap, Wrap.ENABLED)
|
|
||||||
} else {
|
|
||||||
node(node.ifNotWrap, wrap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is Text -> text(node.text)
|
|
||||||
is Line -> {
|
|
||||||
if (wrap.isEnabled()) {
|
|
||||||
newline()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is ForceLine -> newline()
|
|
||||||
is SpaceOrLine -> {
|
|
||||||
if (wrap.isEnabled()) {
|
|
||||||
newline()
|
|
||||||
} else {
|
|
||||||
text(" ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is Space -> text(" ")
|
|
||||||
is Indent -> {
|
|
||||||
if (wrap.isEnabled() && node.nodes.isNotEmpty()) {
|
|
||||||
size += INDENT.length
|
|
||||||
indent++
|
|
||||||
node.nodes.forEach { node(it, wrap) }
|
|
||||||
indent--
|
|
||||||
} else {
|
|
||||||
node.nodes.forEach { node(it, wrap) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is MultilineStringGroup -> {
|
|
||||||
val indentLength = indent * INDENT.length
|
|
||||||
val oldIndent = indentFor(node)
|
|
||||||
var previousNewline = false
|
|
||||||
for ((i, child) in node.nodes.withIndex()) {
|
|
||||||
when {
|
|
||||||
child is ForceLine -> newline(shouldIndent = false) // don't indent
|
|
||||||
child is Text &&
|
|
||||||
previousNewline &&
|
|
||||||
child.text.isBlank() &&
|
|
||||||
child.text.length == oldIndent &&
|
|
||||||
node.nodes[i + 1] is ForceLine -> {}
|
|
||||||
child is Text && previousNewline ->
|
|
||||||
text(reposition(child.text, node.endQuoteCol - 1, indentLength))
|
|
||||||
else -> node(child, Wrap.DETECT) // always detect wrapping
|
|
||||||
}
|
|
||||||
previousNewline = child is ForceLine
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun text(value: String) {
|
|
||||||
if (shouldAddIndent) {
|
|
||||||
repeat(times = indent) { buf.append(INDENT) }
|
|
||||||
shouldAddIndent = false
|
|
||||||
}
|
|
||||||
size += value.length
|
|
||||||
buf.append(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun newline(shouldIndent: Boolean = true) {
|
|
||||||
size = INDENT.length * indent
|
|
||||||
buf.append('\n')
|
|
||||||
shouldAddIndent = shouldIndent
|
|
||||||
}
|
|
||||||
|
|
||||||
// accept text indented by originalOffset characters (tabs or spaces)
|
|
||||||
// and return it indented by newOffset characters (spaces only)
|
|
||||||
private fun reposition(text: String, originalOffset: Int, newOffset: Int): String =
|
|
||||||
" ".repeat(newOffset) + text.drop(originalOffset)
|
|
||||||
|
|
||||||
// Returns the indent of this multiline string, which is the size of the last node before the
|
|
||||||
// closing quotes, or 0 if the closing quotes have no indentation
|
|
||||||
private fun indentFor(multi: MultilineStringGroup): Int {
|
|
||||||
val nodes = multi.nodes
|
|
||||||
if (nodes.size < 2) return 0
|
|
||||||
val beforeLast = nodes[nodes.lastIndex - 1]
|
|
||||||
return if (beforeLast is Text) beforeLast.text.length else 0
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
// max line length
|
|
||||||
const val MAX = 100
|
|
||||||
private const val INDENT = " "
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.formatter
|
|
||||||
|
|
||||||
internal class NaturalOrderComparator(private val ignoreCase: Boolean = false) :
|
|
||||||
Comparator<String> {
|
|
||||||
|
|
||||||
override fun compare(s1: String, s2: String): Int {
|
|
||||||
var i = 0
|
|
||||||
var j = 0
|
|
||||||
|
|
||||||
while (i < s1.length && j < s2.length) {
|
|
||||||
val c1 = if (ignoreCase) s1[i].lowercaseChar() else s1[i]
|
|
||||||
val c2 = if (ignoreCase) s2[j].lowercaseChar() else s2[j]
|
|
||||||
|
|
||||||
if (c1.isDigit() && c2.isDigit()) {
|
|
||||||
val (num1, nextI) = getNumber(s1, i)
|
|
||||||
val (num2, nextJ) = getNumber(s2, j)
|
|
||||||
|
|
||||||
val numComparison = num1.compareTo(num2)
|
|
||||||
if (numComparison != 0) {
|
|
||||||
return numComparison
|
|
||||||
}
|
|
||||||
i = nextI
|
|
||||||
j = nextJ
|
|
||||||
} else {
|
|
||||||
val charComparison = c1.compareTo(c2)
|
|
||||||
if (charComparison != 0) {
|
|
||||||
return charComparison
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return s1.length.compareTo(s2.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getNumber(s: String, startIndex: Int): LongAndInt {
|
|
||||||
var i = startIndex
|
|
||||||
val start = i
|
|
||||||
|
|
||||||
while (i < s.length && s[i].isDigit()) {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
val numStr = s.substring(start, i)
|
|
||||||
val number = numStr.toLongOrNull() ?: 0L
|
|
||||||
return LongAndInt(number, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// use this instead of Pair to avoid boxing
|
|
||||||
private data class LongAndInt(val l: Long, var i: Int)
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.formatter.ast
|
|
||||||
|
|
||||||
import org.pkl.formatter.Generator
|
|
||||||
|
|
||||||
enum class Wrap {
|
|
||||||
ENABLED,
|
|
||||||
DETECT;
|
|
||||||
|
|
||||||
fun isEnabled(): Boolean = this == ENABLED
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed interface FormatNode {
|
|
||||||
fun width(wrapped: Set<Int>): Int =
|
|
||||||
when (this) {
|
|
||||||
is Nodes -> nodes.sumOf { it.width(wrapped) }
|
|
||||||
is Group -> nodes.sumOf { it.width(wrapped) }
|
|
||||||
is Indent -> nodes.sumOf { it.width(wrapped) }
|
|
||||||
is IfWrap -> if (id in wrapped) ifWrap.width(wrapped) else ifNotWrap.width(wrapped)
|
|
||||||
is Text -> text.length
|
|
||||||
SpaceOrLine,
|
|
||||||
Space -> 1
|
|
||||||
ForceLine,
|
|
||||||
is MultilineStringGroup -> Generator.MAX
|
|
||||||
Empty -> 0
|
|
||||||
Line -> 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Text(val text: String) : FormatNode
|
|
||||||
|
|
||||||
object Empty : FormatNode
|
|
||||||
|
|
||||||
object Line : FormatNode
|
|
||||||
|
|
||||||
object ForceLine : FormatNode
|
|
||||||
|
|
||||||
object SpaceOrLine : FormatNode
|
|
||||||
|
|
||||||
object Space : FormatNode
|
|
||||||
|
|
||||||
data class Indent(val nodes: List<FormatNode>) : FormatNode
|
|
||||||
|
|
||||||
data class Nodes(val nodes: List<FormatNode>) : FormatNode
|
|
||||||
|
|
||||||
data class Group(val id: Int, val nodes: List<FormatNode>) : FormatNode
|
|
||||||
|
|
||||||
data class MultilineStringGroup(val endQuoteCol: Int, val nodes: List<FormatNode>) : FormatNode
|
|
||||||
|
|
||||||
data class IfWrap(val id: Int, val ifWrap: FormatNode, val ifNotWrap: FormatNode) : FormatNode
|
|
||||||
Reference in New Issue
Block a user