mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-05 16:35:33 +00:00
145 lines
3.8 KiB
TypeScript
Executable file
145 lines
3.8 KiB
TypeScript
Executable file
#!/usr/bin/env npx ts-node
|
|
|
|
/**
|
|
* Prints a JavaScript regular expression suitable for use in the
|
|
* **overrideScriptDefaults** word breaker decorator.
|
|
*
|
|
* This regular expression matches letters or marks in scripts that
|
|
* conventionally use spaces. This way, the word breaker can make sure to keep
|
|
* contiguous spans of these characters together!
|
|
*
|
|
* If you need to add more Unicode blocks, customize SPACELESS_SCRIPT_BLOCKS.
|
|
*/
|
|
|
|
import {readFileSync} from "fs";
|
|
import * as path from "path";
|
|
|
|
// Where to find UnicodeData.txt and Blocks.txt
|
|
const UCD_DIR = path.join("..", "..", "resources", "standards-data", "unicode-character-database");
|
|
|
|
const SPACELESS_SCRIPT_BLOCKS = new Set([
|
|
"Myanmar", // a.k.a., Burmese
|
|
"Lao",
|
|
"Thai",
|
|
"Khmer",
|
|
"Katakana",
|
|
"Katakana Phonetic Extensions",
|
|
// Add more scripts here, as necessary!
|
|
]);
|
|
|
|
let blockIter = blocks();
|
|
let block = nextBlock();
|
|
|
|
let eligibleCharacters = [];
|
|
|
|
// @ts-ignore: TypeScript complains that it can't compile for..of over a
|
|
// generator without --downlevelIteration, but it works anyway?
|
|
for (let {codePoint, generalCategory} of unicodeData()) {
|
|
if (!block.contains(codePoint)) {
|
|
block = nextBlock();
|
|
}
|
|
console.assert(block.contains(codePoint));
|
|
|
|
if (SPACELESS_SCRIPT_BLOCKS.has(block.name) && isLetterOrMark(generalCategory)) {
|
|
eligibleCharacters.push(codePoint);
|
|
}
|
|
}
|
|
|
|
const ranges = groupCodePointsIntoRanges(eligibleCharacters);
|
|
|
|
const characterClasses = ranges.map(([lower, upper]) => {
|
|
if (lower === upper) {
|
|
return unicodeEscape(lower);
|
|
} else if (lower === upper - 1) {
|
|
return unicodeEscape(lower) + unicodeEscape(upper);
|
|
} else {
|
|
return `${unicodeEscape(lower)}-${unicodeEscape(upper)}`;
|
|
}
|
|
}).join("");
|
|
|
|
console.log(`/**
|
|
* AUTOMATICALLY GENERATED FILE. DO NOT MODIFY
|
|
* See: libexec/create-override-script-regexp.ts for details!
|
|
*/
|
|
export const HAS_SOUTHEAST_ASIAN_LETTER = /[${characterClasses}]/;`);
|
|
|
|
|
|
////////////////////////////////// Helpers ///////////////////////////////////
|
|
|
|
function* unicodeData() {
|
|
let unicodeDataFile = readFileSync(path.join(UCD_DIR, "UnicodeData.txt"), "UTF-8");
|
|
for (let line of unicodeDataFile.split("\n")) {
|
|
if (line.trim() == "") {
|
|
continue;
|
|
}
|
|
|
|
let parts = line.split(";")
|
|
yield {
|
|
codePoint: parseInt(parts[0], 16),
|
|
generalCategory: parts[2],
|
|
}
|
|
}
|
|
}
|
|
|
|
function* blocks() {
|
|
let blocksFile = readFileSync(path.join(UCD_DIR, "Blocks.txt"), "UTF-8");
|
|
for (let line of blocksFile.split("\n")) {
|
|
if (line.trim() === "") {
|
|
continue;
|
|
}
|
|
if (line.startsWith("#")) {
|
|
continue;
|
|
}
|
|
|
|
let [range, name] = line.split("; ");
|
|
let [lower, upper] = range.split("..").map(s => parseInt(s, 16));
|
|
|
|
yield {
|
|
name, lower, upper,
|
|
contains(codePoint: number) {
|
|
return lower <= codePoint && codePoint <= upper;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function nextBlock() {
|
|
let {value} = blockIter.next();
|
|
if (!value) {
|
|
throw new Error("ran out of blocks");
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function isLetterOrMark(category: string): boolean {
|
|
return category.startsWith("L") || category.startsWith("M");
|
|
}
|
|
|
|
function groupCodePointsIntoRanges(characters: number[]): [number, number][] {
|
|
let ranges = [];
|
|
|
|
let previousCharacter = characters[0];
|
|
const candidates = characters.slice(1);
|
|
let currentRange: [number, number] = [previousCharacter, previousCharacter];
|
|
for (let codePoint of candidates) {
|
|
if (codePoint === previousCharacter + 1) {
|
|
currentRange[1] = codePoint;
|
|
} else {
|
|
ranges.push(currentRange);
|
|
currentRange = [codePoint, codePoint];
|
|
}
|
|
|
|
previousCharacter = codePoint;
|
|
}
|
|
|
|
return ranges;
|
|
}
|
|
|
|
function unicodeEscape(codePoint: number) {
|
|
if (codePoint > 0xFFFF) {
|
|
throw new Error("non-BMP code points not supported");
|
|
}
|
|
|
|
let hex = codePoint.toString(16).toUpperCase().padStart(4,'0');
|
|
return `\\u${hex}`;
|
|
}
|