@@ -30,6 +30,12 @@ import { | |||
TENS_ONES_SEPARATOR, | |||
TensName, | |||
THOUSAND, | |||
DoParseState, | |||
ParseGroupsMode, | |||
ParserState, | |||
parseOnes, | |||
parseTenPlusOnes, | |||
parseTens, | |||
} from '../../en/common'; | |||
const FINAL_TOKEN = '' as const; | |||
@@ -61,13 +67,6 @@ export const tokenize = (value: string) => ( | |||
.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 | |||
* parses them. | |||
@@ -219,50 +218,6 @@ const getGroupPlaceFromGroupName = (groupName: string) => { | |||
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 lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; | |||
@@ -337,68 +292,6 @@ const parseFinal = (acc: ParserState): ParserState => { | |||
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. | |||
* @param tokens - The string to parse groups from. | |||
@@ -30,6 +30,12 @@ import { | |||
TENS_ONES_SEPARATOR, | |||
TensName, | |||
THOUSAND, | |||
DoParseState, | |||
ParseGroupsMode, | |||
ParserState, | |||
parseOnes, | |||
parseTens, | |||
parseTenPlusOnes, | |||
} from '../../en/common'; | |||
const FINAL_TOKEN = '' as const; | |||
@@ -55,13 +61,6 @@ export const tokenize = (value: string) => ( | |||
.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 | |||
* parses them. | |||
@@ -217,50 +216,6 @@ const getGroupPlaceFromGroupName = (groupName: string) => { | |||
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 lastGroup = acc.groups.at(-1) ?? [...EMPTY_PLACE]; | |||
@@ -335,68 +290,6 @@ const parseFinal = (acc: ParserState): ParserState => { | |||
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. | |||
* @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; | |||
@@ -341,3 +347,116 @@ export const makeNegative = (s: string) => { | |||
const negativePrefix = `${NEGATIVE} `; | |||
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], | |||
}; | |||
}; |