diff --git a/pkl-core/src/main/java/org/pkl/core/util/yaml/YamlEscaper.java b/pkl-core/src/main/java/org/pkl/core/util/yaml/YamlEscaper.java index 29a21dae..afd32482 100644 --- a/pkl-core/src/main/java/org/pkl/core/util/yaml/YamlEscaper.java +++ b/pkl-core/src/main/java/org/pkl/core/util/yaml/YamlEscaper.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,33 +19,55 @@ import org.pkl.core.util.AbstractCharEscaper; import org.pkl.core.util.IoUtils; import org.pkl.core.util.Nullable; -// https://yaml.org/spec/1.2.2/#57-escaped-characters +/** + * Emits escape sequences for YAML. This is only used when emitting double-quoted strings. + * + *

Note: we don't need to escape space ({@code 0x20}) because we don't generate quoted multiline + * strings. We also don't need to escape forward slash ({@code 0x2f}) because a normal forward slash + * is also valid YAML. + * + * @see https://yaml.org/spec/1.2.2/#57-escaped-characters + */ public final class YamlEscaper extends AbstractCharEscaper { private static final String[] REPLACEMENTS; static { - REPLACEMENTS = new String[0x22 + 1]; + REPLACEMENTS = new String[0xA0 + 1]; for (var i = 0; i < 0x20; i++) { REPLACEMENTS[i] = IoUtils.toHexEscape(i); } + // ns-esc-null REPLACEMENTS[0x00] = "\\0"; + // ns-esc-bell REPLACEMENTS[0x07] = "\\a"; + // ns-esc-backspace REPLACEMENTS[0x08] = "\\b"; + // ns-esc-horizontal-tab REPLACEMENTS[0x09] = "\\t"; + // ns-esc-line-feed REPLACEMENTS[0x0A] = "\\n"; + // ns-esc-vertical-tab REPLACEMENTS[0x0B] = "\\v"; + // ns-esc-form-feed REPLACEMENTS[0x0C] = "\\f"; + // ns-esc-carriage-return REPLACEMENTS[0x0D] = "\\r"; + // ns-esc-escape REPLACEMENTS[0x1B] = "\\e"; - // we don't ever need to escape 0x20 because we don't generate quoted multiline strings + // ns-esc-double-quote REPLACEMENTS[0x22] = "\\\""; + // ns-esc-backslash + REPLACEMENTS[0x5c] = "\\\\"; + // ns-esc-next-line + REPLACEMENTS[0x85] = "\\N"; + // ns-esc-non-breaking-space + REPLACEMENTS[0xA0] = "\\_"; } @Override protected @Nullable String findReplacement(char ch) { //noinspection UnnecessaryUnicodeEscape - return ch <= '\u0022' - ? REPLACEMENTS[ch] - : ch == '\u2028' ? "\\L" : ch == '\u2029' ? "\\P" : null; + return ch <= 0xA0 ? REPLACEMENTS[ch] : ch == '\u2028' ? "\\L" : ch == '\u2029' ? "\\P" : null; } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/api/yamlRenderer9.yml.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/api/yamlRenderer9.yml.pkl new file mode 100644 index 00000000..344d0f27 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/api/yamlRenderer9.yml.pkl @@ -0,0 +1,36 @@ +// test escaping in double quotes +// every string is prefixed with `\t` s.t. YamlRenderer will emit double-quoted strings. + +`null` = "\t\u{00}" + +bell = "\t\u{7}" + +backspace = "\t\u{8}" + +horizontalTab = "\t" + +lineFeed = "\t\u{a}" + +verticalTab = "\t\u{b}" + +formFeed = "\t\u{c}" + +carriageReturn = "\t\r" + +escape = "\t\u{1b}" + +doubleQuote = "\t\"" + +backslash = "\t\\" + +nextLine = "\t\u{85}" + +nbsp = "\t\u{a0}" + +lineSep = "\t\u{2028}" + +paragraphSep = "\t\u{2029}" + +output { + renderer = new YamlRenderer {} +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/api/yamlRendererStringsCompat.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/api/yamlRendererStringsCompat.pkl index b648b507..7bf2c28b 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/api/yamlRendererStringsCompat.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/api/yamlRendererStringsCompat.pkl @@ -114,12 +114,12 @@ examples { render(".NAN") render(".nAn") // never float } - + ["tag like strings"] { "!!bool true" "!!str my string value" } - + ["number like string keys"] { render(new Dynamic { `0` = "0" diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/api/yamlRenderer9.yml b/pkl-core/src/test/files/LanguageSnippetTests/output/api/yamlRenderer9.yml new file mode 100644 index 00000000..4196874f --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/api/yamlRenderer9.yml @@ -0,0 +1,15 @@ +'null': "\t\0" +bell: "\t\a" +backspace: "\t\b" +horizontalTab: "\t" +lineFeed: "\t\n" +verticalTab: "\t\v" +formFeed: "\t\f" +carriageReturn: "\t\r" +escape: "\t\e" +doubleQuote: "\t\"" +backslash: "\t\\" +nextLine: "\t\N" +nbsp: "\t\_" +lineSep: "\t\L" +paragraphSep: "\t\P"