News for hackers. Stuff that matters.
Re:UTF-8 Everywhere (2012) by dietrichepp on Sunday June 19, 2016 @05:14PM (#11934698)
There are a couple cases for string indexing, usually involving parsing or regular expressions. You might want to slice the quotes off of a quoted string, or slice from one match of a regular expression to a match of a different regular expression starting at a different index. These come up infrequently enough that it doesn't make sense to make a better API just for these use cases, but frequently enough that it would be a serious impediment if we didn't do some kind of string indexing.

I agree, however, that it's completely irrelevant whether the indexes correspond to code units (i.e. byte offsets in UTF-8) or whether they correspond to code points (how it works in Python currently), as long as we have some way to store, compare, and otherwise manipulate locations within a string.

Some Rust developers at one point proposed making string indexes their own (opaque) type, as you suggest, so that they couldn't be confused with integers used for other purposes. The extra complexity of such an API meant that this proposal was never really taken seriously, and it only prevents a small category of programming errors.

You might be interested in looking at some string APIs which are mostly without string indexing, like Haskell's Data.Text, which is one of the most well-designed string APIs ever made.

https://hackage.haskell.org/package/text-1.2.2.1/docs/Data-T...

As for Windows, my Windows apps use UTF-8 everywhere, and then convert to wchar_t at the last possible moment when interacting with the Windows API. I believe this is what UTF-8 Everywhere suggests.

Re:UTF-8 Everywhere (2012) by Avernar on Sunday June 19, 2016 @05:48PM (#11934852)
> You might want to slice the quotes off of a quoted string

That's why I really like dealing with UTF-8. As you said you can just index by byte instead of having to worry about code point boundries. This is because a code point will never match inside a larger code point.

So if I search for a one byte quote character it will never match the second, third or fourth bytes in a larger code point. Same with other control characters.

Works when searching for 2 or 3 byte code points as well.

Re:UTF-8 Everywhere (2012) by bluejekyll on Sunday June 19, 2016 @11:37PM (#11935909)
> in Rust... The extra complexity of such an API meant that this proposal was never really taken seriously,

In fact it looks like even standard indexes have been deprecated, in favor of Iterators over the string:

https://doc.rust-lang.org/stable/std/primitive.str.html#meth...

Iteration is better IMO.

Re:UTF-8 Everywhere (2012) by johncolanduoni on Monday June 20, 2016 @02:10AM (#11936331)
Makes a lot of sense. I get frustrated with how explicit I have to be with Rust sometimes (currently I'm writing something with a lot of reusable, immutable structures and am put off by how much Rc.clone() that requires) but making it known to the programmer that every indexing of a String is effectively an iteration is a good call.
Re:UTF-8 Everywhere (2012) by bluejekyll on Monday June 20, 2016 @10:58AM (#11938429)
Yeah, this is a tough thing when coming from other languages.

I've been using references pretty effectively to get around some issues like this, though in other cases Rc is my only resort.

If you have some code up on github, I'd be happy to take a look and see if there's some other options that are less cumbersome.

Re:UTF-8 Everywhere (2012) by Animats on Sunday June 19, 2016 @08:04PM (#11935384)
The advantage of an opaque type is that you should be able to add one to it and advance one rune. Go copies the string functions from C, but doesn't (at least as of last year) offer "advance one rune" and "back up one rune" functions.
Re:UTF-8 Everywhere (2012) by Avernar on Sunday June 19, 2016 @08:11PM (#11935399)
If by rune you mean code point that doesn't really gain you anything. If you are searching for something it's faster to just scan a byte at a time.

If you want to split the string into "characters" you need to do it a the grapheme level (multiple code points) and for that you need to use a unicode library. But that adds overhead when you're just scanning.

A JSON or XML scanner does not need the added overhead of advancing by code point or grapheme.

Re:UTF-8 Everywhere (2012) by dietrichepp on Sunday June 19, 2016 @10:30PM (#11935760)
I don't understand the argument for "characters = grapheme clusters". From my perspective, there are a lot of different ways you'd want to iterate over a string. Grapheme cluster breaks, tailored grapheme cluster breaks, word breaks, line breaks, code points, code units… all of these make sense in some context. However, there are precious few times that I've wanted to iterate over grapheme clusters, so telling people that they should do that instead of something else doesn't make sense to me. (I mean, what problem is so common that we would want to iterate this way by default?)

For parsing, it often makes sense to iterate over code points or code units, since many languages are defined in terms of code points (and you can translate that to code units, for performance). XML 1.1, JavaScript, Haskell, etc... many languages are defined in terms of the underlying code points and their character classes in the Unicode standard. JSON and XML 1.0 are not everything.

Re:UTF-8 Everywhere (2012) by Avernar on Sunday June 19, 2016 @11:19PM (#11935863)
We're pretty much on the same page here. When you want to slice a string (because you can only display or store a certain amount) or you want to do text selection and other cursor operations you can't so it by code point. That's where you want to break at character boundries which are graphemes or grapheme clusters.

For parsing it's easier to just scan for a byte sequence in UTF-8 because you know what you're looking for ahead of time. If you're looking for a matching quote, brace, etc. you just need to scan for a single byte in your text stream. Adding a smart iterator to the process that moves to the start of each code unit is not necessary and will slow things way down.

I just gave JSON and XML as examples and not an exhaustive list. If you know the code points you are scanning for it's way more effecient to scan for their code units. The state machine in a paraer will be operating at the byte level anyways.

I have yet to see a good example where processing/iterating by code point is the better choice (other than the grapheme code of the unicode library).

Re:UTF-8 Everywhere (2012) by dietrichepp on Monday June 20, 2016 @03:48AM (#11936662)
I'm not convinced that state machines will operate at the byte level. First of all, not all tokenizers are written using state machines. Even if that is the mathematical language we use to talk about parsers, it's still relatively common to make hand-written parsers. Secondly, if you take a Unicode-specified language and convert it to a state machine that operates on UTF-8, you can easily end up with an explosion in the number of possible states. Remember, this trick doesn't really change the size of the transition table, it just spreads it out among more states. On the other hand, you can get a lot more mileage out of using equivalency classes, as long as you're using something sensible like code points to begin with.

If you're curious, here's the V8 tokenizer header file:

https://github.com/v8/v8/blob/master/src/parsing/scanner.h

You can see that it works on an underlying UTF-16 code unit stream which is then composed into code points before tokenization. This extra step with UTF-16 is a quirk of JavaScript.

If you think that V8 shouldn't be processing by code point, feel free to explain that to them.

Re:UTF-8 Everywhere (2012) by Avernar on Monday June 20, 2016 @04:39AM (#11936824)
State machines would have to operate on the byte level. Otherwise each state would have to have have 65536 entries per state. The trick to handle UTF-8 would be to have 0-127 run as a state machine and > 127 break out to functions to handle the various unicode ranges that are valid for identifiers.

For languages that only allow non ascii in string literals a pure state machine would suffice.

Not sure why you're mentioning parsers. At that point you you're dealing with tokens.

As for UTF-16 it's an ugly hack that never should have existed in the first place. Unfortunately the unicode people had to fix their UCS-2 mistake.

Since Javascript is standardised to be either UCS-2 or UTF-16 it probably made sense to make the scanner use UTF-16.

Re:UTF-8 Everywhere (2012) by dietrichepp on Wednesday June 22, 2016 @10:13AM (#11954071)
State machines don't have to operate on the byte level because the tables can use equivalency classes. This will often result in smaller and faster state machines than byte-level state machines, if your language uses Unicode character classes here and there.
Re:UTF-8 Everywhere (2012) by Avernar on Monday June 20, 2016 @04:53AM (#11936870)
Looks like Javascript source code is required to be processed as UTF-16:

ECMAScript source text is represented as a sequence of characters in the Unicode character encoding, version 3.0 or later. [...] ECMAScript source text is assumed to be a sequence of 16-bit code units for the purposes of this specification. [...] If an actual source text is encoded in a form other than 16-bit code units it must be processed as if it was first converted to UTF-16.

Re:UTF-8 Everywhere (2012) by dietrichepp on Wednesday June 22, 2016 @10:14AM (#11954079)
Right, but the UTF-16 is read code point by code point, not code unit by code unit. At that point, it might as well be UTF-8 or UTF-32.
Re:UTF-8 Everywhere (2012) by ridiculous_fish on Monday June 20, 2016 @02:32AM (#11936414)
Walking over grapheme clusters is common in UIs, e.g. visually truncating a string.

Really I think you are arguing against the notion of "default iteration" altogether. As you say, the right type of iteration is context dependent, and it ought to be made explicit.

Re:UTF-8 Everywhere (2012) by dietrichepp on Monday June 20, 2016 @03:32AM (#11936614)
I'm not sure it is so common in UIs. Truncation is done by a single library function, so that's one case where it's used. Another case is for character wrapping, but that's fairly uncommon. I'm having trouble coming up with another case where it's used. Font shaping is done by a font shaping engine, which applies an enormous number of rules specific to the script in use. Text in a text editor isn't deleted according to grapheme cluster boundaries, and the text cursor doesn't fall on grapheme cluster boundaries either. These are all rules that change according to the script in use.
Re:UTF-8 Everywhere (2012) by infogulch on Monday June 20, 2016 @01:46AM (#11936247)
Actually, go has supported ranging over code points of a string since Go 1 [0], and has unicode/utf8.DecodeRuneInString which produces the same iteration sequence as range.

[0]: https://blog.golang.org/strings