Skip to content

Commit

Permalink
Consolidate commonly used Hangul Unicode helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
mooniker committed Sep 23, 2019
1 parent f1ac1f7 commit fc8afc3
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 48 deletions.
56 changes: 56 additions & 0 deletions hangul.computations.js
@@ -0,0 +1,56 @@
const { SBase, NCount, TCount, SCount } = require("./hangul.constraints");

/**
* Returns an integer division quotient (rounded down)
*
* @param {number} dividend
* @param {number} divisor
*/
const intDiv = (dividend, divisor) => Math.floor(dividend / divisor);

/**
*
* @param {(string|integer)} s
*/
function computeSIndex(s) {
const SIndex = (typeof s === "string" ? s.charCodeAt(0) : s) - SBase;

if (0 > SIndex || SIndex >= SCount) {
throw new Error(`Not a Hangul syllable: ${s}`);
}

return SIndex;
}

/**
*
* @param {integer} SIndex
*/
const computeLIndex = SIndex => intDiv(SIndex, NCount); // integer division rounded down

/**
*
* @param {integer} SIndex
*/
const computeVIndex = SIndex => intDiv(SIndex % NCount, TCount);

/**
*
* @param {integer} SIndex
*/
const computeTIndex = SIndex => SIndex % TCount;

/**
*
* @param {integer} SIndex
*/
const computeLVIndex = SIndex => (SIndex / TCount) * TCount;

module.exports = {
intDiv,
computeSIndex,
computeLIndex,
computeVIndex,
computeTIndex,
computeLVIndex
};
28 changes: 28 additions & 0 deletions hangul.computations.test.js
@@ -0,0 +1,28 @@
const {
intDiv,
computeSIndex,
computeVIndex,
computeLIndex
} = require("./hangul.computations");

describe("Integer division function", () => {
test("should divide 5 by 2 and round the quotient down to the nearest integer", () => {
expect(intDiv(5, 2)).toBe(2);
});

test("should divide 4 by 2 and arrive at an integer quotient (no rounding needed)", () => {
expect(intDiv(4, 2)).toBe(2);
});
});

describe("computeSIndex function", () => {
test.todo("should be tested");
});

describe("computeVIndex function", () => {
test.todo("should be tested");
});

describe("computeLIndex function", () => {
test.todo("should be tested");
});
25 changes: 11 additions & 14 deletions hangul.constraints.js
@@ -1,16 +1,15 @@
// Common constraints (based on the pseudocode provided in the Unicode spec)

const LCount = 19;
const VCount = 21;
const TCount = 28; // one more than the number of trailing consonants relevant to the decomposition algorithm: (0x11C2 - 0x11A8 + 1) + 1

/*
Number of precomposed Hangul syllables starting with the same leading consonant, counting both
- LV_Syllables and
- LVT_Syllables
for each possible trailing consonant
*/
const NCount = VCount * TCount; // 588
const LCount = 19; // number of lead consonants in Hangul

// Number of LV_Syllables per each leading consonant
const VCount = 21; // i.e. number of vowels in Hangul

// Number of LVT_Syllables per each possible trailing consonant
const TCount = 28; // ie. the number of letters in Hangul

// Number of precomposed Hangul syllables
const NCount = VCount * TCount; // VCount * TCount = 588

module.exports = {
SBase: 0xac00,
Expand All @@ -23,9 +22,7 @@ module.exports = {
LCount,
VCount,
TCount,

// Number of precomposed Hangul syllables
NCount, // 588,
NCount, // 588

// Total number of precomposed Hangul syllables (11172)
SCount: LCount * NCount
Expand Down
39 changes: 16 additions & 23 deletions hangul.decompose.js
@@ -1,19 +1,12 @@
const {
SBase,
LBase,
VBase,
TBase,
NCount,
TCount
} = require("./hangul.constraints");
const { SBase, LBase, VBase, TBase } = require("./hangul.constraints");

/**
* Returns an integer division quotient (rounded down)
*
* @param {number} dividend
* @param {number} divisor
*/
const intDiv = (dividend, divisor) => Math.floor(dividend / divisor);
const {
computeSIndex,
computeLIndex,
computeVIndex,
computeLVIndex,
computeTIndex
} = require("./hangul.computations");

/**
* Based on "Arithmetic Decomposition Mapping" as described in Unicode core spec for "LV" Hangul syllable types
Expand All @@ -22,12 +15,13 @@ const intDiv = (dividend, divisor) => Math.floor(dividend / divisor);
* @returns {array}
*/
function arithmeticDecompositionMappingLV(s) {
const SIndex = (typeof s === "string" ? s.charCodeAt(0) : s) - SBase;
const SIndex = computeSIndex(s);
const LIndex = computeLIndex(SIndex);
const VIndex = computeVIndex(SIndex);

const LIndex = intDiv(SIndex, NCount); // integer division rounded down
const VIndex = intDiv(SIndex % NCount, TCount);
const LPart = LBase + LIndex;
const VPart = VBase + VIndex;

return [LPart, VPart];
}

Expand All @@ -38,10 +32,10 @@ function arithmeticDecompositionMappingLV(s) {
* @returns {array}
*/
function arithmeticDecompositionMappingLVT(s) {
const SIndex = (typeof s === "string" ? s.charCodeAt(0) : s) - SBase;
const SIndex = computeSIndex(s);
const LVIndex = computeLVIndex(SIndex);
const TIndex = computeTIndex(SIndex);

const LVIndex = (SIndex / TCount) * TCount;
const TIndex = SIndex % TCount;
const LVPart = SBase + LVIndex;
const TPart = TBase + TIndex;

Expand Down Expand Up @@ -69,7 +63,7 @@ function decomposeHangulChar(s) {
const SIndex = (typeof s === "string" ? s.charCodeAt(0) : s) - SBase;

const LVPart = arithmeticDecompositionMappingLV(s);
const TIndex = SIndex % TCount;
const TIndex = computeTIndex(SIndex);

if (TIndex > 0) {
const TPart = TBase + TIndex;
Expand All @@ -88,7 +82,6 @@ function decomposeHangulChar(s) {
const decomposeHangul = word => [...word].map(decomposeHangulChar);

module.exports = {
intDiv,
arithmeticDecompositionMappingLV,
arithmeticDecompositionMappingLVT,
decomposeHangulChar,
Expand Down
11 changes: 0 additions & 11 deletions hangul.decompose.test.js
@@ -1,5 +1,4 @@
const {
intDiv,
arithmeticDecompositionMappingLV,
arithmeticDecompositionMappingLVT,
decomposeHangulChar,
Expand All @@ -22,16 +21,6 @@ const hangulHexCases = {
: [0x1111, 0x1171, 0x11b6]
};

describe("intDiv", () => {
test("should divide 5 by 2 and round the quotient down to the nearest integer", () => {
expect(intDiv(5, 2)).toBe(2);
});

test("should divide 4 by 2 and arrive at an integer quotient (no rounding needed)", () => {
expect(intDiv(4, 2)).toBe(2);
});
});

describe("arithmeticDecompositionMappingLV", () => {
test("should pull out correct code points for ㅍ and ㅟ from 0xd4db (퓛)", () => {
expect(arithmeticDecompositionMappingLV(0xd4db)).toStrictEqual([
Expand Down

0 comments on commit fc8afc3

Please sign in to comment.