A half-open range of the source text, in UTF-16 code units.
Code units rather than code points, and rather than bytes, because that is
what a MoonBit String is indexed in and what error-report labels are
measured in. Choosing anything else would put a conversion on every slice
and every diagnostic; choosing this one puts it nowhere.
This duplicates css/span almost exactly, and it should. Lifting thirty
lines into a shared micro-module would put a dependency in the manifest of
both, which every consumer of either would then fetch -- and adding one is a
design decision rather than a manifest edit. Thirty duplicated lines is the
cheaper side of that trade.
There is deliberately no line or column here. A span is a range, and a range
is enough to slice the source and to hand to a renderer, which computes line
and column from the text itself. Carrying them would mean keeping them
correct through every construction, for the benefit of nothing that cannot
recompute them.