Skip to content

fix: use codepoint count for std.format width padding - #1091

Merged
stephenamar-db merged 1 commit into
databricks:masterfrom
He-Pin:fix/format-width-codepoint-count
Jul 28, 2026
Merged

fix: use codepoint count for std.format width padding#1091
stephenamar-db merged 1 commit into
databricks:masterfrom
He-Pin:fix/format-width-codepoint-count

Conversation

@He-Pin

@He-Pin He-Pin commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Motivation

std.format("%5s", "😀") produced 3 spaces instead of 4 because width was computed using String.length (UTF-16 code units), counting the surrogate pair as 2. Python's % formatting defines width as codepoint count; go-jsonnet uses len([]rune(str)). Both yield 4 spaces.

Mathematical basis: Python 3 strings are sequences of Unicode codepoints. The % operator's width field specifies "minimum field width" measured in characters (codepoints), not UTF-16 code units or bytes.

Modification

In widen, use codePointCount for non-numeric (string) paths where supplementary characters can appear. Numeric paths (%d/%f/%g/%x/%o) retain O(1) .length since their output is provably ASCII — avoiding O(n) scans on every numeric format call.

Result

Width padding counts supplementary characters as 1 codepoint, matching go-jsonnet and Python. Numeric formatting performance is unchanged.

Behavior comparison

Input go-jsonnet jrsonnet Python sjsonnet (before) sjsonnet (after)
"%5s" % "😀" " 😀" (4 spaces) " 😀" (1 space, byte-len) " 😀" (4 spaces) " 😀" (3 spaces) " 😀" (4 spaces)
"%-5s" % "😀" "😀 " (4 spaces) "😀 " (1 space) "😀 " (4 spaces) "😀 " (3 spaces) "😀 " (4 spaces)
"%5s" % "a😀b" " a😀b" (2 spaces) " a😀b" (2 spaces) " a😀b" (1 space) " a😀b" (2 spaces)
"%5s" % "hello" "hello" "hello" "hello" "hello" "hello" (unchanged)
"%05d" % 42 "00042" "00042" "00042" "00042" "00042" (unchanged)

Note: jrsonnet uses UTF-8 byte length (a third behavior); go-jsonnet is the reference implementation.

Test plan

  • Added format_width_codepoint.jsonnet regression test with golden file
  • "%5s" % "😀" → 4 spaces + emoji (matches go-jsonnet/Python)
  • "%5.1s" % "😀x" → 4 spaces + emoji (precision + width combined)
  • ASCII strings unaffected (numeric path uses .length)
  • Full test suite passes

@He-Pin
He-Pin force-pushed the fix/format-width-codepoint-count branch from 103888f to 0407ce7 Compare July 27, 2026 19:02
Motivation:
`std.format("%5s", "😀")` produced 3 spaces instead of 4 because width
was computed using String.length (UTF-16 code units), counting the
surrogate pair as 2. Python's % formatting defines width as codepoint
count; go-jsonnet uses `len([]rune(str))`. Both yield 4 spaces.

Modification:
In `widen`, use `codePointCount` for non-numeric (string) paths where
supplementary characters can appear. Numeric paths (%d/%f/%g/%x/%o)
retain O(1) `.length` since their output is provably ASCII. This avoids
O(n) scans on every numeric format call while fixing the string case.

Result:
Width padding counts supplementary characters as 1 codepoint, matching
go-jsonnet and Python. Numeric formatting performance is unchanged.

References:
- Python docs: width is "minimum field width" in characters (codepoints)
- go-jsonnet: uses len([]rune(str)) for width calculation
@He-Pin
He-Pin force-pushed the fix/format-width-codepoint-count branch from 0407ce7 to bc2e2e0 Compare July 28, 2026 02:51
@stephenamar-db
stephenamar-db merged commit 13cdd18 into databricks:master Jul 28, 2026
5 checks passed
CertainLach added a commit to deltarocks/jrsonnet that referenced this pull request Jul 28, 2026
Ref: databricks/sjsonnet#1091
Change-Id: Iaf87474402255661590bc69989c801626a6a6964
Signed-off-by: Yaroslav Bolyukin <iam@lach.pw>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants