ww source: align UTF-8 BOM placement

This commit is contained in:
2026-08-21 20:34:10 +09:00
parent ee4573bf55
commit 9381f8fb8e
11 changed files with 764 additions and 5 deletions

View File

@@ -8427,6 +8427,124 @@ Rejected source creates no persisted byte contract, while valid source bytes
are unchanged. Build workdir format remains `18`, test workdir format remains
`19`, and semantic storage format remains `3`.
### 11.42 Implemented per-source UTF-8 BOM placement
Every eligible physical WW source may begin with one UTF-8-encoded U+FEFF byte
order mark (`EF BB BF`). That marker is ignored, and the following token keeps
its three-byte source position at line 1, column 4. U+FEFF at any later raw
source position is
rejected once as `invalid BOM in the middle of the file`, including inside a
line/block comment, string, or rune. Two leading markers therefore ignore the
first and reject the second. A truncated marker or another invalid UTF-8 byte
sequence retains the ordinary byte-error path; UTF-16 source is not introduced.
#### Pinned Go evidence and applicability
The sole authority is official Go 1.26.5 at commit
`c19862e5f8415b4f24b189d065ed739517c548ba`:
- compiler reader `(*source).nextch` decodes UTF-8, skips U+FEFF at its first
source position, and reports `invalid BOM in the middle of the file`
elsewhere
([`cmd/compile/internal/syntax/source.go`, lines 113165](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/syntax/source.go#L113-L165));
- public scanner `(*Scanner).next` rejects later U+FEFF and `(*Scanner).Init`
consumes the first one
([`go/scanner/scanner.go`, lines 58100 and 128164](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/scanner/scanner.go#L58-L164));
- scanner tests assert the ignored first marker and later markers between
tokens, in comments, runes, and strings
([`go/scanner/scanner_test.go`, lines 371374 and 812815](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/scanner/scanner_test.go#L371-L374),
[`go/scanner/scanner_test.go`, lines 812815](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/scanner/scanner_test.go#L812-L815));
- compiler-scanner `TestScanErrors` asserts the positioned later-marker error
([`cmd/compile/internal/syntax/scanner_test.go`, lines 587599](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/syntax/scanner_test.go#L587-L599)); and
- official command testdata places the marker before `package main` and loads
that source's imports and embedded file
([`cmd/go/testdata/script/build_ignore_leading_bom.txt`, lines 125](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/build_ignore_leading_bom.txt#L1-L25)).
Those source branches and assertions are **behavior directly implemented or
asserted by pinned Go**. Applying the first position independently to every WW
physical source before its existing synthetic aggregate boundary is **behavior
derived from the pinned implementation**. The pre-fix Cstage/WWstage failures
on leading markers and successes for markers in comments/strings were
**directly measured WW behavior**.
The behavior honestly applies inside WW's local, dotted-import, manifest-free
model: it is source representation before package and import interpretation.
It requires no module, manifest, registry, lock, cache, database, CAS, network
resolution, generalized import syntax, build expression, or normalized
identity.
#### Ownership and four-axis result
`lexinit`, `lpeek`, `lget`, and `lexnext` in `cmd/wcc/lex.c`, with their twins
in `lib/ww/syntax/lex.ww`, are the language owners. They skip the exact initial
marker while advancing the logical column by its encoded width, recognize a
later marker as one code point in every lexical context, and emit one
stage-identical diagnostic.
`sep_emit_body` in `cmd/ww/main.c` and `sepemitbody` in
`selfhost/cmd/ww/main.ww` replace the optional marker with three spaces in every
physical body placed after a synthetic `//ww:module-reset`, preserving columns;
direct and imports-only lexer input remains independently correct. The shared package
coordinator's `pkgclause` in `internal/wwpackage/package.ww` begins its
pre-discovery package-name scan after the same optional marker. It does not
replace the complete stage-driver scan.
- **Go-like build:** a selected command, library, or imported dependency may
use the marker in each eligible source. Multiple physical marked sources
compose normally, command publication succeeds, and the executable runs
normally. A later marker rejects during source loading before graph/action
construction or compiler, assembler, archiver, linker, install, or runtime
work. A source error keeps precedence over a missing import.
- **Go-like test:** production, same-package, external-package, and test-only
files each receive the offset-zero allowance. Valid variants build and run
through the ordinary single directory product. A later marker yields the
existing attributable `FAIL\n` result without a variant, generated main,
test process, accounting, or retained binary.
- **Go-like package:** package-clause recognition now begins at the pinned
logical source start in direct compilers, directory drivers, and the shared
coordinator. Declared names, source roles, package conflicts, command/test
family selection, and canonical identity do not change.
- **Go-like import:** imports following a legal marker and imports in a marked
dependency retain their exact source spelling, file scope, qualifier,
contextual local/vendor resolution, case-sensitive canonical dotted
identity, visibility, cycle, and initialization behavior. The marker never
becomes an edge or identity component.
Fixed-target filename selection remains earlier than parsing: an excluded
`_windows.ww` or `_windows_test.ww` contributes no marker diagnostic, package,
import, graph, action, artifact, or invalidation state. For valid inputs, graph
nodes, scheduling, producer arguments, initialization, runtime, result order,
and publication are unchanged. Legal marker bytes become three
position-preserving spaces only in the synthetic unit. Adding or removing the
marker therefore changes unit content and invalidates source-derived actions,
while semantic interface, assembly, object, archive, initializer, and
executable content remains the same; every form remains Cstage/WWstage
byte-identical.
Cold rejection leaves no product, work generation, adjacent scratch, capture,
`.new`, `.install`, or `.wwtxn.*`. A warm later-marker edit preserves the
entire committed generation, tool vouchers, stamp, and public executable.
Restoring the exact legal leading form recreates the same unit and reuses committed
compiler/assembler/archive work before the normal link/publication boundary.
Concurrent valid and invalid requests keep independent lexer/coordinator state,
workdirs, captures, diagnostics, and products. Because later-marker rejection
occurs before a producer or test child, producer/runtime failure, signals,
timeouts, interruption, and process-group cleanup gain no new branch; their
existing owners remain authoritative.
The C lexer unit, WW syntax unit, and WW-native
`utf8_bom_is_per_source_and_only_first` observer prove initial position,
later-marker lexical contexts, direct frontend diagnostics and assembly,
selected/imported multi-source build, wrong-target exclusion, every test source
role, test-only execution, syntax-before-resolution precedence, cold cleanup,
warm rollback and reuse, concurrent isolation, publication/runtime behavior,
and complete stage diagnostic/artifact parity.
No persisted format changes. Previously valid marker-free bytes are
unchanged; previously leading-marked requests could not commit a generation;
and diagnostic text is not a persisted-byte contract. Build workdir format
remains `18`, test workdir format remains `19`, and semantic storage format
remains `3`.
## 12. Candidate architectures and hard-gate decision
Five candidates were developed as coherent systems, not as feature bins.

View File

@@ -52,8 +52,13 @@ classes from §2 (`ident`, `int_lit`, …).
### 2.1 Source representation
Source is UTF-8. The lexer operates on bytes; non-ASCII bytes are legal
only inside string and rune literals and comments.
Source is UTF-8. One UTF-8-encoded byte order mark (U+FEFF, bytes
`EF BB BF`) is ignored when it is the first code point of a physical source
file. Its three encoded bytes still count in source positions, so a following
token on the first line begins at column 4. U+FEFF is invalid at
every other source position, including inside string and rune literals and
comments. Apart from that marker rule, the lexer operates on bytes and
non-ASCII bytes are legal only inside string and rune literals and comments.
### 2.2 Comments

View File

@@ -415,6 +415,20 @@ generation. The directory command retains its existing attributable final
diagnostic. This ordering state is per source and never package, import,
variant, action, artifact, publication, or persistence identity.
Every eligible source also owns an independent source-start position. An exact
UTF-8 BOM (`EF BB BF`) at byte offset zero is ignored before package-clause and
import parsing, so production, same-package, external-package, and test-only
sources all count the marker's three bytes and begin the following token at
logical line 1 column 4. The same code point anywhere else,
including a comment or literal, is a source error before variant actions,
generated main, producers, test runtime, accounting, or retained publication.
The shared coordinator applies the offset-zero rule while classifying package
clauses; the selected stage driver scans the complete source and preserves the
per-file rule while composing package units. Wrong-target files remain excluded
before either operation. The marker is source representation only and never
package/import/action/artifact/publication/persistence identity; no test result
is cached.
After that eligibility boundary and the coordinator's required package-clause
classification and production `@test` validation parses, the delegated loader
performs selected-basename Go 1.26.5 simple-fold preflight before its graph