Skip to content

Commit 2f7fb60

Browse files
JLHwungmathiasbynens
authored andcommitted
Apply the AL default for unlisted assigned Line_Break code points
Before Unicode 4.0, LineBreak.txt gives unlisted assigned characters the value AL, and only unassigned code points XX. The parser gave every unlisted code point XX, so the private-use code points in Unicode 3.1.0 through 3.2.0 had Line_Break=Unknown instead of Alphabetic. Assisted-By: Claude Opus 5.5 Issue: #110
1 parent 3d2b3e3 commit 2f7fb60

2 files changed

Lines changed: 41 additions & 14 deletions

File tree

‎scripts/parse-line-break.mjs‎

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import valueAliases from 'unicode-property-value-aliases';
22
import utils from './utils.mjs';
33
import regenerate from 'regenerate';
4+
import { parseDerivedGeneralCategory } from './parse-blocks-scripts-properties.mjs';
45

56
const aliases = valueAliases.get('Line_Break');
67

@@ -24,30 +25,37 @@ const parseLineBreak = async (version) => {
2425
// are given the value `XX`.
2526
Unknown: regenerate().addRange(0, 0x10ffff),
2627
};
28+
// Before Unicode 4.0, only unassigned code points default to `XX`:
29+
// “Assigned characters that are not listed explicitly are given the
30+
// value "AL".”
31+
const assignedDefault =
32+
/Assigned characters that are not listed explicitly are given the value\s*#?\s*"(\w+)"/.exec(
33+
source,
34+
)?.[1];
2735
const lines = source.split('\n');
2836
for (const line of lines) {
2937
const trimmed = line.trim();
3038
if (!trimmed || trimmed.startsWith('#')) {
3139
continue;
3240
}
3341
const data = trimmed.split(';');
34-
const charRange = data[0].replace('..', '-').trim();
35-
const rangeParts = charRange.split('-');
3642
const value = data[1].split('#')[0].trim();
3743
const canonicalName = findCanonicalName(value);
44+
const [from, to = from] = data[0]
45+
.trim()
46+
.split('..')
47+
.map((codePoint) => parseInt(codePoint, 16));
3848
map[canonicalName] ??= regenerate();
39-
if (rangeParts.length === 2) {
40-
const [from, to] = [
41-
parseInt(rangeParts[0], 16),
42-
parseInt(rangeParts[1], 16),
43-
];
44-
map['Unknown'].removeRange(from, to);
45-
map[canonicalName].addRange(from, to);
46-
} else {
47-
const codePoint = parseInt(charRange, 16);
48-
map['Unknown'].remove(codePoint);
49-
map[canonicalName].add(codePoint);
50-
}
49+
map['Unknown'].removeRange(from, to);
50+
map[canonicalName].addRange(from, to);
51+
}
52+
if (assignedDefault) {
53+
const canonicalName = findCanonicalName(assignedDefault);
54+
const { Assigned } = await parseDerivedGeneralCategory(version);
55+
const unlistedAssigned = map['Unknown'].clone().intersection(Assigned);
56+
map['Unknown'].remove(unlistedAssigned);
57+
map[canonicalName] ??= regenerate();
58+
map[canonicalName].add(unlistedAssigned);
5159
}
5260

5361
return map;

‎tests/parsers.mjs‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import parseIndicConjunctBreak from '../scripts/parse-indic-conjunct-break.mjs';
99
import parseJoiningType from '../scripts/parse-joining-type.mjs';
1010
import parseSimpleCaseMapping from '../scripts/parse-simple-case-mapping.mjs';
1111
import parseNameAliases from '../scripts/parse-name-aliases.mjs';
12+
import parseLineBreak from '../scripts/parse-line-break.mjs';
1213
import {
1314
parseBlocks,
1415
parseDerivedCoreProperties,
@@ -387,6 +388,24 @@ test('Historical Script values use canonical names', async (t) => {
387388
}
388389
});
389390

391+
test('Early Line_Break data gives unlisted assigned code points AL', async (t) => {
392+
for (const version of ['3.1.0', '3.1.1', '3.2.0']) {
393+
const lineBreak = await parseLineBreak(version);
394+
for (const cp of [0xe000, 0xf8ff, 0xf0000, 0x10fffd]) {
395+
const message = `${version}: ${cp.toString(16)}`;
396+
t.assert.ok(lineBreak.Alphabetic.contains(cp), message);
397+
t.assert.ok(!lineBreak.Unknown.contains(cp), message);
398+
}
399+
// Unassigned code points keep XX.
400+
t.assert.ok(lineBreak.Unknown.contains(0x0378), version);
401+
t.assert.ok(lineBreak.Unknown.contains(0xfffff), version);
402+
}
403+
// Unicode 4.0 gives every unlisted code point XX.
404+
const lineBreak = await parseLineBreak('4.0.0');
405+
t.assert.ok(lineBreak.Unknown.contains(0xe000));
406+
t.assert.ok(!lineBreak.Alphabetic.contains(0xe000));
407+
});
408+
390409
test('Unicode 3.1.1 uses its corrected property list', async (t) => {
391410
const v310 = await parseProperties('3.1.0');
392411
const v311 = await parseProperties('3.1.1');

0 commit comments

Comments
 (0)