@@ -30,6 +30,12 @@ import { | |||||
TENS_ONES_SEPARATOR, | TENS_ONES_SEPARATOR, | ||||
TensName, | TensName, | ||||
THOUSAND, | THOUSAND, | ||||
DoParseState, | |||||
ParseGroupsMode, | |||||
ParserState, | |||||
parseOnes, | |||||
parseTenPlusOnes, | |||||
parseTens, | |||||
} from '../../en/common'; | } from '../../en/common'; | ||||
const FINAL_TOKEN = '' as const; | const FINAL_TOKEN = '' as const; | ||||
@@ -61,13 +67,6 @@ export const tokenize = (value: string) => ( | |||||
.filter((maybeToken) => maybeToken.length > 0) | .filter((maybeToken) => maybeToken.length > 0) | ||||
); | ); | ||||
interface DoParseState { | |||||
groupNameCurrent: string; | |||||
millias: number[]; | |||||
milliaIndex: number; | |||||
done: boolean; | |||||
} | |||||
/** | /** | ||||
* Deconstructs a group name token (e.g. "million", "duodecillion", etc.) to its affixes and | * Deconstructs a group name token (e.g. "million", "duodecillion", etc.) to its affixes and | ||||
* parses them. | * parses them. | ||||
@@ -219,50 +218,6 @@ const getGroupPlaceFromGroupName = (groupName: string) => { | |||||
return bigGroupPlace * BigInt(2) + (groupName.startsWith(THOUSAND) ? BigInt(1) : BigInt(0)); | return bigGroupPlace * BigInt(2) + (groupName.startsWith(THOUSAND) ? BigInt(1) : BigInt(0)); | ||||
}; | }; | ||||
/** | |||||
* Mode of the group parser. | |||||
*/ | |||||
enum ParseGroupsMode { | |||||
/** | |||||
* Initial mode. | |||||
*/ | |||||
INITIAL = 'initial', | |||||
/** | |||||
* Has parsed a ones name. | |||||
*/ | |||||
ONES_MODE = 'ones', | |||||
/** | |||||
* Has parsed a tens name. | |||||
*/ | |||||
TENS_MODE = 'tens', | |||||
/** | |||||
* Has parsed a ten-plus-ones name. | |||||
*/ | |||||
TEN_PLUS_ONES_MODE = 'tenPlusOnes', | |||||
/** | |||||
* Has parsed a "hundred" token. | |||||
*/ | |||||
HUNDRED_MODE = 'hundred', | |||||
/** | |||||
* Has parsed a "thousand" or any "-illion" token. | |||||
*/ | |||||
THOUSAND_MODE = 'thousand', | |||||
/** | |||||
* Done parsing. | |||||
*/ | |||||
DONE = 'done', | |||||
} | |||||
/** | |||||
* State of the group parser. | |||||
*/ | |||||
interface ParserState { | |||||
lastToken?: string; | |||||
groups: Group[]; | |||||
mode: ParseGroupsMode; | |||||
negative: boolean; | |||||
} | |||||
const parseThousand = (acc: ParserState, token: string): ParserState => { | const parseThousand = (acc: ParserState, token: string): ParserState => { | ||||
const lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; | const lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; | ||||
@@ -337,68 +292,6 @@ const parseFinal = (acc: ParserState): ParserState => { | |||||
return acc; | return acc; | ||||
}; | }; | ||||
const parseOnes = (acc: ParserState, token: string): ParserState => { | |||||
if (acc.mode === ParseGroupsMode.THOUSAND_MODE) { | |||||
// Create next empty place | |||||
return { | |||||
...acc, | |||||
lastToken: token, | |||||
mode: ParseGroupsMode.ONES_MODE, | |||||
groups: [...acc.groups, [...EMPTY_PLACE]], | |||||
}; | |||||
} | |||||
return { | |||||
...acc, | |||||
lastToken: token, | |||||
mode: ParseGroupsMode.ONES_MODE, | |||||
}; | |||||
}; | |||||
const parseTenPlusOnes = (acc: ParserState, token: string): ParserState => { | |||||
const tenPlusOnes = TEN_PLUS_ONES.findIndex((t) => t === token); | |||||
const lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; | |||||
if (acc.mode === ParseGroupsMode.THOUSAND_MODE) { | |||||
return { | |||||
...acc, | |||||
lastToken: token, | |||||
mode: ParseGroupsMode.TEN_PLUS_ONES_MODE, | |||||
groups: [...acc.groups, [`01${tenPlusOnes}` as GroupDigits, lastGroup[GROUP_PLACE_INDEX] - BigInt(1)]], | |||||
}; | |||||
} | |||||
lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 1)}1${tenPlusOnes}` as GroupDigits; | |||||
return { | |||||
...acc, | |||||
lastToken: token, | |||||
mode: ParseGroupsMode.TEN_PLUS_ONES_MODE, | |||||
groups: [...acc.groups.slice(0, -1), lastGroup], | |||||
}; | |||||
}; | |||||
const parseTens = (acc: ParserState, token: string): ParserState => { | |||||
const tens = TENS.findIndex((t) => t === token); | |||||
const lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; | |||||
if (acc.mode === ParseGroupsMode.THOUSAND_MODE) { | |||||
return { | |||||
...acc, | |||||
lastToken: token, | |||||
mode: ParseGroupsMode.TENS_MODE, | |||||
groups: [...acc.groups, [`0${tens}0` as GroupDigits, lastGroup[GROUP_PLACE_INDEX] - BigInt(1)]], | |||||
}; | |||||
} | |||||
lastGroup[GROUP_DIGITS_INDEX] = ( | |||||
`${lastGroup[GROUP_DIGITS_INDEX].slice(0, 1)}${tens}${lastGroup[GROUP_DIGITS_INDEX].slice(2)}` | |||||
) as GroupDigits; | |||||
return { | |||||
...acc, | |||||
lastToken: token, | |||||
mode: ParseGroupsMode.TENS_MODE, | |||||
groups: [...acc.groups.slice(0, -1), lastGroup], | |||||
}; | |||||
}; | |||||
/** | /** | ||||
* Parses groups from a string. | * Parses groups from a string. | ||||
* @param tokens - The string to parse groups from. | * @param tokens - The string to parse groups from. | ||||
@@ -30,6 +30,12 @@ import { | |||||
TENS_ONES_SEPARATOR, | TENS_ONES_SEPARATOR, | ||||
TensName, | TensName, | ||||
THOUSAND, | THOUSAND, | ||||
DoParseState, | |||||
ParseGroupsMode, | |||||
ParserState, | |||||
parseOnes, | |||||
parseTens, | |||||
parseTenPlusOnes, | |||||
} from '../../en/common'; | } from '../../en/common'; | ||||
const FINAL_TOKEN = '' as const; | const FINAL_TOKEN = '' as const; | ||||
@@ -55,13 +61,6 @@ export const tokenize = (value: string) => ( | |||||
.filter((maybeToken) => maybeToken.length > 0) | .filter((maybeToken) => maybeToken.length > 0) | ||||
); | ); | ||||
interface DoParseState { | |||||
groupNameCurrent: string; | |||||
millias: number[]; | |||||
milliaIndex: number; | |||||
done: boolean; | |||||
} | |||||
/** | /** | ||||
* Deconstructs a group name token (e.g. "million", "duodecillion", etc.) to its affixes and | * Deconstructs a group name token (e.g. "million", "duodecillion", etc.) to its affixes and | ||||
* parses them. | * parses them. | ||||
@@ -217,50 +216,6 @@ const getGroupPlaceFromGroupName = (groupName: string) => { | |||||
return bigGroupPlace + BigInt(1); | return bigGroupPlace + BigInt(1); | ||||
}; | }; | ||||
/** | |||||
* Mode of the group parser. | |||||
*/ | |||||
enum ParseGroupsMode { | |||||
/** | |||||
* Initial mode. | |||||
*/ | |||||
INITIAL = 'initial', | |||||
/** | |||||
* Has parsed a ones name. | |||||
*/ | |||||
ONES_MODE = 'ones', | |||||
/** | |||||
* Has parsed a tens name. | |||||
*/ | |||||
TENS_MODE = 'tens', | |||||
/** | |||||
* Has parsed a ten-plus-ones name. | |||||
*/ | |||||
TEN_PLUS_ONES_MODE = 'tenPlusOnes', | |||||
/** | |||||
* Has parsed a "hundred" token. | |||||
*/ | |||||
HUNDRED_MODE = 'hundred', | |||||
/** | |||||
* Has parsed a "thousand" or any "-illion" token. | |||||
*/ | |||||
THOUSAND_MODE = 'thousand', | |||||
/** | |||||
* Done parsing. | |||||
*/ | |||||
DONE = 'done', | |||||
} | |||||
/** | |||||
* State of the group parser. | |||||
*/ | |||||
interface ParserState { | |||||
lastToken?: string; | |||||
groups: Group[]; | |||||
mode: ParseGroupsMode; | |||||
negative: boolean; | |||||
} | |||||
const parseThousand = (acc: ParserState, token: string): ParserState => { | const parseThousand = (acc: ParserState, token: string): ParserState => { | ||||
const lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; | const lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; | ||||
@@ -335,68 +290,6 @@ const parseFinal = (acc: ParserState): ParserState => { | |||||
return acc; | return acc; | ||||
}; | }; | ||||
const parseOnes = (acc: ParserState, token: string): ParserState => { | |||||
if (acc.mode === ParseGroupsMode.THOUSAND_MODE) { | |||||
// Create next empty place | |||||
return { | |||||
...acc, | |||||
lastToken: token, | |||||
mode: ParseGroupsMode.ONES_MODE, | |||||
groups: [...acc.groups, [...EMPTY_PLACE]], | |||||
}; | |||||
} | |||||
return { | |||||
...acc, | |||||
lastToken: token, | |||||
mode: ParseGroupsMode.ONES_MODE, | |||||
}; | |||||
}; | |||||
const parseTenPlusOnes = (acc: ParserState, token: string): ParserState => { | |||||
const tenPlusOnes = TEN_PLUS_ONES.findIndex((t) => t === token); | |||||
const lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; | |||||
if (acc.mode === ParseGroupsMode.THOUSAND_MODE) { | |||||
return { | |||||
...acc, | |||||
lastToken: token, | |||||
mode: ParseGroupsMode.TEN_PLUS_ONES_MODE, | |||||
groups: [...acc.groups, [`01${tenPlusOnes}` as GroupDigits, lastGroup[GROUP_PLACE_INDEX] - BigInt(1)]], | |||||
}; | |||||
} | |||||
lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 1)}1${tenPlusOnes}` as GroupDigits; | |||||
return { | |||||
...acc, | |||||
lastToken: token, | |||||
mode: ParseGroupsMode.TEN_PLUS_ONES_MODE, | |||||
groups: [...acc.groups.slice(0, -1), lastGroup], | |||||
}; | |||||
}; | |||||
const parseTens = (acc: ParserState, token: string): ParserState => { | |||||
const tens = TENS.findIndex((t) => t === token); | |||||
const lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; | |||||
if (acc.mode === ParseGroupsMode.THOUSAND_MODE) { | |||||
return { | |||||
...acc, | |||||
lastToken: token, | |||||
mode: ParseGroupsMode.TENS_MODE, | |||||
groups: [...acc.groups, [`0${tens}0` as GroupDigits, lastGroup[GROUP_PLACE_INDEX] - BigInt(1)]], | |||||
}; | |||||
} | |||||
lastGroup[GROUP_DIGITS_INDEX] = ( | |||||
`${lastGroup[GROUP_DIGITS_INDEX].slice(0, 1)}${tens}${lastGroup[GROUP_DIGITS_INDEX].slice(2)}` | |||||
) as GroupDigits; | |||||
return { | |||||
...acc, | |||||
lastToken: token, | |||||
mode: ParseGroupsMode.TENS_MODE, | |||||
groups: [...acc.groups.slice(0, -1), lastGroup], | |||||
}; | |||||
}; | |||||
/** | /** | ||||
* Parses groups from a string. | * Parses groups from a string. | ||||
* @param tokens - The string to parse groups from. | * @param tokens - The string to parse groups from. | ||||
@@ -1,4 +1,10 @@ | |||||
import { Group, GroupPlace } from '../../common'; | |||||
import { | |||||
Group, | |||||
GROUP_DIGITS_INDEX, | |||||
GROUP_PLACE_INDEX, | |||||
GroupDigits, | |||||
GroupPlace, | |||||
} from '../../common'; | |||||
export const GROUP_SEPARATOR = ' ' as const; | export const GROUP_SEPARATOR = ' ' as const; | ||||
@@ -341,3 +347,116 @@ export const makeNegative = (s: string) => { | |||||
const negativePrefix = `${NEGATIVE} `; | const negativePrefix = `${NEGATIVE} `; | ||||
return s.startsWith(negativePrefix) ? s.slice(negativePrefix.length) : `${negativePrefix}${s}`; | return s.startsWith(negativePrefix) ? s.slice(negativePrefix.length) : `${negativePrefix}${s}`; | ||||
}; | }; | ||||
export interface DoParseState { | |||||
groupNameCurrent: string; | |||||
millias: number[]; | |||||
milliaIndex: number; | |||||
done: boolean; | |||||
} | |||||
/** | |||||
* Mode of the group parser. | |||||
*/ | |||||
export enum ParseGroupsMode { | |||||
/** | |||||
* Initial mode. | |||||
*/ | |||||
INITIAL = 'initial', | |||||
/** | |||||
* Has parsed a ones name. | |||||
*/ | |||||
ONES_MODE = 'ones', | |||||
/** | |||||
* Has parsed a tens name. | |||||
*/ | |||||
TENS_MODE = 'tens', | |||||
/** | |||||
* Has parsed a ten-plus-ones name. | |||||
*/ | |||||
TEN_PLUS_ONES_MODE = 'tenPlusOnes', | |||||
/** | |||||
* Has parsed a "hundred" token. | |||||
*/ | |||||
HUNDRED_MODE = 'hundred', | |||||
/** | |||||
* Has parsed a "thousand" or any "-illion" token. | |||||
*/ | |||||
THOUSAND_MODE = 'thousand', | |||||
/** | |||||
* Done parsing. | |||||
*/ | |||||
DONE = 'done', | |||||
} | |||||
/** | |||||
* State of the group parser. | |||||
*/ | |||||
export interface ParserState { | |||||
lastToken?: string; | |||||
groups: Group[]; | |||||
mode: ParseGroupsMode; | |||||
negative: boolean; | |||||
} | |||||
export const parseOnes = (acc: ParserState, token: string): ParserState => { | |||||
if (acc.mode === ParseGroupsMode.THOUSAND_MODE) { | |||||
// Create next empty place | |||||
return { | |||||
...acc, | |||||
lastToken: token, | |||||
mode: ParseGroupsMode.ONES_MODE, | |||||
groups: [...acc.groups, [...EMPTY_PLACE]], | |||||
}; | |||||
} | |||||
return { | |||||
...acc, | |||||
lastToken: token, | |||||
mode: ParseGroupsMode.ONES_MODE, | |||||
}; | |||||
}; | |||||
export const parseTenPlusOnes = (acc: ParserState, token: string): ParserState => { | |||||
const tenPlusOnes = TEN_PLUS_ONES.findIndex((t) => t === token); | |||||
const lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; | |||||
if (acc.mode === ParseGroupsMode.THOUSAND_MODE) { | |||||
return { | |||||
...acc, | |||||
lastToken: token, | |||||
mode: ParseGroupsMode.TEN_PLUS_ONES_MODE, | |||||
groups: [...acc.groups, [`01${tenPlusOnes}` as GroupDigits, lastGroup[GROUP_PLACE_INDEX] - BigInt(1)]], | |||||
}; | |||||
} | |||||
lastGroup[GROUP_DIGITS_INDEX] = `${lastGroup[GROUP_DIGITS_INDEX].slice(0, 1)}1${tenPlusOnes}` as GroupDigits; | |||||
return { | |||||
...acc, | |||||
lastToken: token, | |||||
mode: ParseGroupsMode.TEN_PLUS_ONES_MODE, | |||||
groups: [...acc.groups.slice(0, -1), lastGroup], | |||||
}; | |||||
}; | |||||
export const parseTens = (acc: ParserState, token: string): ParserState => { | |||||
const tens = TENS.findIndex((t) => t === token); | |||||
const lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; | |||||
if (acc.mode === ParseGroupsMode.THOUSAND_MODE) { | |||||
return { | |||||
...acc, | |||||
lastToken: token, | |||||
mode: ParseGroupsMode.TENS_MODE, | |||||
groups: [...acc.groups, [`0${tens}0` as GroupDigits, lastGroup[GROUP_PLACE_INDEX] - BigInt(1)]], | |||||
}; | |||||
} | |||||
lastGroup[GROUP_DIGITS_INDEX] = ( | |||||
`${lastGroup[GROUP_DIGITS_INDEX].slice(0, 1)}${tens}${lastGroup[GROUP_DIGITS_INDEX].slice(2)}` | |||||
) as GroupDigits; | |||||
return { | |||||
...acc, | |||||
lastToken: token, | |||||
mode: ParseGroupsMode.TENS_MODE, | |||||
groups: [...acc.groups.slice(0, -1), lastGroup], | |||||
}; | |||||
}; |