Add parlance::BidiLevel - #710
Conversation
| /// The base bidi level of the paragraph of text. | ||
| #[inline(always)] | ||
| pub fn paragraph_level(&self) -> u8 { | ||
| pub fn paragraph_level(&self) -> BidiLevel { | ||
| self.paragraph_level | ||
| } | ||
|
|
||
| /// Whether the paragraph's resolved base direction is right-to-left. | ||
| #[inline(always)] | ||
| pub fn is_rtl(&self) -> bool { | ||
| !self.paragraph_level.is_multiple_of(2) | ||
| } |
There was a problem hiding this comment.
Removing Analysis::is_rtl in favor of Analysis::paragraph_level().is_rtl(), as per #708 (comment).
In that same thread, Bruce pointed out we have some inconsistency in the code base, sometimes calling this "base level", sometimes "paragraph level". The spec uses both words, but uses "paragraph level" a bit more. The code uses "base level" more. In any case, we probably should still rename this, either to something like base_bidi_level or paragraph_bidi_level.
The motivation is to have a `repr(transparent)` wrapper for bidi levels to hold documentation and useful methods (e.g., letting users call `level.is_rtl()` instead of manually writing `!level.is_multiple_of(2)`), while being able to bytemuck between `BidiLevel` <=> `u8` for interop.
e483121 to
2d1f829
Compare
|
We could similarly implement |
nicoburns
left a comment
There was a problem hiding this comment.
Very nice. Good use of parlance.
| #[inline(always)] | ||
| const fn next_odd(level: BidiLevel) -> BidiLevel { | ||
| BidiLevel::new((level.to_u8() + 1) | 1) | ||
| } | ||
|
|
||
| #[inline(always)] | ||
| const fn next_even(level: BidiLevel) -> BidiLevel { | ||
| BidiLevel::new((level.to_u8() + 2) & !1) | ||
| } |
There was a problem hiding this comment.
These could optionally be methods on the type
There was a problem hiding this comment.
The lack of capping to 125 here would be my main hesitance, but I think on balance I agree (also with it being optional/up to your discretion)
There was a problem hiding this comment.
I've added the methods, with some documentation about wrapping.
There was a problem hiding this comment.
Nothing blocking, some code style nits.
I'm really happy to see this type properly documented. I did try and start on that task, but it got too big for what I had time to do.
This probably should have changelog entries (what is parley_engine's changelog story?)
| /// These are numbers indicating how deeply bidirectional embeddings are nested in the text, and the | ||
| /// default direction of text on that level. Even levels are left-to-right, odd levels are | ||
| /// right-to-left. Normally, the minimum level is 0 (left-to-right), and the maximum level, | ||
| /// according to [UAX #9 § 3.1.1 BD2][uax-bd2], is 125. |
There was a problem hiding this comment.
Incidentally, this being 125 does give us a bit to muck about with. I don't know of any use for that (maybe for whether rule L1 would apply to this?).
| #[repr(transparent)] | ||
| pub struct BidiLevel(u8); | ||
|
|
||
| impl BidiLevel { |
There was a problem hiding this comment.
Does it make sense for max level to be stored here, either a u8 or BidiLevel constant?
There was a problem hiding this comment.
That sounds sensible. I've added BidiLevel::MAX, but perhaps you have feedback on the naming.
In particular, it's not the greatest value BidiLevel itself can represent.
| pub struct BidiLevel(u8); | ||
|
|
||
| impl BidiLevel { | ||
| /// Construct a new bidi level. |
There was a problem hiding this comment.
I'd claim that we should debug assert that this is less than or equal to 125, but not blockingly so.
There was a problem hiding this comment.
Perhaps that makes sense. This ties back into #710 (comment).
Deferring this for now.
| // Safety: The struct is `repr(transparent)`, wrapping a `u8`. | ||
| // | ||
| // While generally BidiLevels have a maximum of 125, no value is unsound. | ||
| unsafe impl Pod for BidiLevel {} | ||
|
|
||
| // Safety: The struct is `repr(transparent)`, wrapping a `u8`. | ||
| unsafe impl Zeroable for BidiLevel {} |
There was a problem hiding this comment.
We probably should have the usual safety tests for these. But also this is so simple it seems hard to imagine it going wrong! I'm not even sure what the tests would look like? Maybe even just that the size is 1 to force this to be revisited?
There was a problem hiding this comment.
I've added the size test. I'm also not sure whether we can do better.
| /// Returns `true` if the run has right-to-left directionality. | ||
| pub fn is_rtl(&self) -> bool { | ||
| self.shaped.bidi_level & 1 != 0 | ||
| self.shaped.bidi_level.is_rtl() | ||
| } |
There was a problem hiding this comment.
I guess nominally it might be better to just return the bidi level here, but that's not done here to avoid breaking changes?
There was a problem hiding this comment.
Yeah, I intentionally did not change the parley API here.
That said, we probably should break it. (But not in this PR.)
| } | ||
|
|
||
| fn expect_bidi_embed_level_list(self, expected: Vec<u8>) -> Self { | ||
| fn expect_bidi_embed_level_list(self, expected: &[u8]) -> Self { |
| #[inline(always)] | ||
| const fn next_odd(level: BidiLevel) -> BidiLevel { | ||
| BidiLevel::new((level.to_u8() + 1) | 1) | ||
| } | ||
|
|
||
| #[inline(always)] | ||
| const fn next_even(level: BidiLevel) -> BidiLevel { | ||
| BidiLevel::new((level.to_u8() + 2) & !1) | ||
| } |
There was a problem hiding this comment.
The lack of capping to 125 here would be my main hesitance, but I think on balance I agree (also with it being optional/up to your discretion)
I've added a changelog entry for Parlance. I think we should write something in the changelog for |
The motivation is to have a
repr(transparent)wrapper for bidi levels to hold documentation and useful methods (e.g., letting users calllevel.is_rtl()instead of manually writing!level.is_multiple_of(2), or having our APIs vary betweenlevel() -> u8andis_rtl() -> boolmethods), while allowing bytemucking betweenBidiLevel<=>u8for interop.parley_enginenow returnsBidiLevel, but I've keptparley's API unchanged.