diff --git a/bun.lockb b/bun.lockb index 39df61b..219bc71 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/src/locale.test-d.ts b/src/locale.test-d.ts index caeaaf1..440340c 100644 --- a/src/locale.test-d.ts +++ b/src/locale.test-d.ts @@ -1,10 +1,22 @@ +/** + * NOTE: + * This test is work in pregoress ... + * We might remove this test file in the future, + * when we will find out that cannot support locale validation + */ + import { expectTypeOf, test } from 'vitest' import type { CheckRange, + ParseKeyword, ParseLangSubtag, + ParseOtherExtension, + ParsePuExtension, ParseRegionSubtag, ParseScriptSubtag, + ParseTransformedExtension, + ParseUnicodeExtension, ParseUnicodeLanguageId, ParseVariantsSubtag, } from './locale.ts' @@ -59,23 +71,25 @@ test('ParseLangSubtag', () => { // 2 chars expectTypeOf>().toMatchTypeOf< - ['ja', never] + ['ja', never, []] >() // 3 chars - expectTypeOf>().toMatchTypeOf< - ['jpn', never] + expectTypeOf>().toMatchTypeOf< + ['jpn', never, []] >() // 7 chars - expectTypeOf>().toMatchTypeOf<['english', never]>() + expectTypeOf>().toMatchTypeOf< + ['english', never, ['US']] + >() // 'root' is special (4 chars) - expectTypeOf>().toMatchTypeOf<['root', never]>() + expectTypeOf>().toMatchTypeOf<['root', never, []]>() // upper case expectTypeOf>().toMatchTypeOf< - ['JA', never] + ['JA', never, []] >() // mixied case expectTypeOf>().toMatchTypeOf< - ['Ja', never] + ['Ja', never, []] >() /** @@ -84,22 +98,22 @@ test('ParseLangSubtag', () => { // empty expectTypeOf>().toMatchTypeOf< - [never, 1] + [never, 1, []] >() // no-alphabet expectTypeOf>().toMatchTypeOf< - [never, 2] + [never, 2, []] >() // never expectTypeOf>().toMatchTypeOf< - [never, 1] + [never, 1, []] >() // range expectTypeOf>().toMatchTypeOf< - [never, 3] + [never, 3, []] >() expectTypeOf>().toMatchTypeOf< - [never, 3] + [never, 3, []] >() // not string expectTypeOf>().toMatchTypeOf() @@ -112,17 +126,17 @@ test('ParseScriptSubtag', () => { // 4 chars expectTypeOf>().toMatchTypeOf< - ['kana', never] + ['kana', never, []] >() // empty expectTypeOf>().toMatchTypeOf< - [never, never] + [never, never, []] >() // upper case expectTypeOf>().toMatchTypeOf< - ['Kana', never] + ['Kana', never, []] >() /** @@ -131,19 +145,26 @@ test('ParseScriptSubtag', () => { // no-alphabet expectTypeOf>().toMatchTypeOf< - [never, 4] + [never, 4, []] >() // range expectTypeOf>().toMatchTypeOf< - [never, 5] + [never, 5, []] >() expectTypeOf>().toMatchTypeOf< - [never, 5] + [never, 5, []] >() // not string expectTypeOf>().toMatchTypeOf< never >() + + /** + * through case + */ + expectTypeOf>().toMatchTypeOf< + [never, never, ['US']] + >() }) test('ParseRegionSubtag', () => { @@ -153,19 +174,19 @@ test('ParseRegionSubtag', () => { // 2 chars (alpha) expectTypeOf>().toMatchTypeOf< - ['jp', never] + ['jp', never, []] >() // 3 chars (digit) expectTypeOf>().toMatchTypeOf< - ['012', never] + ['012', never, []] >() // empty expectTypeOf>().toMatchTypeOf< - [never, never] + [never, never, []] >() // upper case expectTypeOf>().toMatchTypeOf< - ['JP', never] + ['JP', never, []] >() /** @@ -174,29 +195,36 @@ test('ParseRegionSubtag', () => { // no all-alphabet expectTypeOf>().toMatchTypeOf< - [never, 6] + [never, 6, []] >() // no all-digits expectTypeOf>().toMatchTypeOf< - [never, 6] + [never, 6, []] >() // range expectTypeOf>().toMatchTypeOf< - [never, 7] + [never, 7, []] >() expectTypeOf>().toMatchTypeOf< - [never, 7] + [never, 7, []] >() expectTypeOf>().toMatchTypeOf< - [never, 7] + [never, 7, []] >() expectTypeOf>().toMatchTypeOf< - [never, 7] + [never, 7, []] >() // not string expectTypeOf>().toMatchTypeOf< never >() + + /** + * through case + */ + expectTypeOf>().toMatchTypeOf< + [never, never, ['u']] + >() }) test('ParseVariantsSubtag', () => { @@ -206,20 +234,21 @@ test('ParseVariantsSubtag', () => { // 3 chars, all digits expectTypeOf>().toMatchTypeOf< - [['123'], never] + [['123'], never, []] >() // 3 chars, first digit and alphabets expectTypeOf>().toMatchTypeOf< - [['1ab'], never] + [['1ab'], never, []] >() // 5 chars, all alphabets expectTypeOf>().toMatchTypeOf< - [['abcde'], never] + [['abcde'], never, []] >() // 7 chars, alphabets and digits - expectTypeOf>().toMatchTypeOf< - [['ab12cde', 'abcde123'], never] - >() + expectTypeOf>() + .toMatchTypeOf< + [['ab12cde', 'abcde123'], never, []] + >() /** * Failed cases @@ -227,33 +256,33 @@ test('ParseVariantsSubtag', () => { // range 1 expectTypeOf>().toMatchTypeOf< - [[], never] + [[], never, ['1']] >() // range 2 expectTypeOf>().toMatchTypeOf< - [[], never] + [[], never, ['12']] >() // range 4 expectTypeOf>().toMatchTypeOf< - [[], never] + [[], never, ['1234']] >() // range 9 expectTypeOf>().toMatchTypeOf< - [[], never] + [[], never, ['123456789']] >() // 3 chars, first alphabet and digits expectTypeOf>().toMatchTypeOf< - [[], never] + [[], never, ['a12']] >() // 3 chars, all alphabets expectTypeOf>().toMatchTypeOf< - [[], never] + [[], never, ['abc']] >() // not string expectTypeOf>().toMatchTypeOf< - [[], never] + [[], never, [1]] >() }) @@ -261,20 +290,216 @@ test('ParseUnicodeLangugageId', () => { /** * Success cases */ + expectTypeOf>().toMatchTypeOf< + [ + { lang: 'ja'; variants: [] }, + never, + [], + ] + >() + expectTypeOf>().toMatchTypeOf< + [ + { lang: 'ja'; region: 'JP'; variants: [] }, + never, + [], + ] + >() expectTypeOf>().toMatchTypeOf< - [{ lang: 'ja'; script: 'Kana'; region: 'jp'; variants: ['jauer'] }, never] + [ + { lang: 'ja'; script: 'Kana'; region: 'jp'; variants: ['jauer'] }, + never, + [], + ] >() /** Erros */ expectTypeOf>().toMatchTypeOf< [ - { lang: never; script: never; region: never; variants: ['jauer'] }, + { lang: never; script: never; region: never; variants: [] }, [ 'requires 2-3 or 5-8 alphabet lower characters', - 'unicode script subtag requires 4 alphabet lower characters', - 'unicode region subtag requires 2 alphabet lower characters or 3 digits', - 'duplicate unicode variant subtag', ], + ['a', 'ana', 'p', 'jauer', 'jauer'], ] >() }) + +test('ParseKeyword', () => { + /** + * Success cases + */ + // type t1 = ParseKeyword<['co', 'standard', 'phonetic']> + expectTypeOf>().toMatchTypeOf< + ['co', 'standard-phonetic', []] + >() + // type t2 = ParseKeyword<['co', 'standard']> + expectTypeOf>().toMatchTypeOf< + ['co', 'standard', []] + >() + expectTypeOf>().toMatchTypeOf< + ['co', '', []] + >() + + /** Fail cases */ + // type t3 = ParseKeyword<['c']> + expectTypeOf>().toMatchTypeOf< + [never, ['c']] + >() +}) + +test('ParseUnicodeExtension', () => { + /** + * Success cases + */ + // type t1 = ParseUnicodeExtension<['co', 'standard']> + expectTypeOf>() + .toMatchTypeOf< + [{ type: 'u'; keywords: ['co', 'standard']; attributes: [] }, never, []] + >() + // type t2 = ParseUnicodeExtension<['foo', 'bar', 'co', 'standard']> + expectTypeOf>() + .toMatchTypeOf< + [ + { type: 'u'; keywords: ['co', 'standard']; attributes: ['foo', 'bar'] }, + never, + [], + ] + >() + + /** + * Fail cases + */ + // type t3 = ParseUnicodeExtension<['c']> + expectTypeOf>().toMatchTypeOf< + [ + { type: 'u'; keywords: []; attributes: [] }, + never, + ['c'], + ] + >() +}) + +test('ParseTransformedExtension', () => { + /** + * Success cases + */ + // type t1 = ParseTransformedExtension< + // ['en', 'Kana', 'US', 'jauer', 'h0', 'hybrid'] + // > + expectTypeOf< + ParseTransformedExtension<['en', 'Kana', 'US', 'jauer', 'h0', 'hybrid']> + >() + .toMatchTypeOf< + [ + { + type: 't' + lang: { + lang: 'en' + script: 'Kana' + region: 'US' + variants: ['jauer'] + } + fields: [['h0', 'hybrid']] + }, + never, + [], + ] + >() + + /** + * Fail cases + */ + // type t2 = ParseTransformedExtension<['en', 'US', 'h0']> + expectTypeOf>() + .toMatchTypeOf< + [ + { + type: 't' + lang: { + lang: 'en' + script: never + region: 'US' + variants: [] + } + fields: never + }, + 10, + ['h0'], + ] + >() + // type t3 = ParseTransformedExtension<['en']> + expectTypeOf>().toMatchTypeOf< + [never, 11, ['en']] + >() +}) + +test('ParsePuExtension', () => { + /** + * Success cases + */ + expectTypeOf< + ParsePuExtension<['1234', 'abcde']> + >() + .toMatchTypeOf< + [ + { + type: 'x' + value: '1234-abcde' + }, + never, + [], + ] + >() + + /** + * Fail cases + */ + // empty + expectTypeOf< + ParsePuExtension<['']> + >() + .toMatchTypeOf< + [ + never, + 12, + [''], + ] + >() + + // not alphabet or digit + expectTypeOf< + ParsePuExtension<['1あ']> + >() + .toMatchTypeOf< + [ + never, + 12, + ['1あ'], + ] + >() +}) + +test('ParseOtherExtension', () => { + /** + * Success cases + */ + expectTypeOf< + ParseOtherExtension<['1234', 'abcde']> + >() + .toMatchTypeOf<['1234-abcde', []]>() + + /** + * Fail cases + */ + // empty + expectTypeOf< + ParseOtherExtension<['']> + >() + .toMatchTypeOf<['', ['']]>() + + // not alphabet or digit + expectTypeOf< + ParseOtherExtension<['1あ']> + >() + .toMatchTypeOf<['', ['1あ']]>() +}) diff --git a/src/locale.ts b/src/locale.ts index cd73685..62795e5 100644 --- a/src/locale.ts +++ b/src/locale.ts @@ -1,3 +1,10 @@ +/** + * NOTE: + * This test is work in pregoress ... + * We might remove this test file in the future, + * when we will find out that cannot support locale validation + */ + import type { All, Concat, @@ -5,6 +12,7 @@ import type { First, Includes, IsNever, + Join, Length, Push, Shift, @@ -21,6 +29,56 @@ export interface UnicodeLocaleId { > } +export type KV = [string, string] | [string] + +export interface Extension { + type: string +} + +export interface UnicodeExtension extends Extension { + type: 'u' + keywords: KV[] + attributes: string[] +} + +export interface TransformedExtension extends Extension { + type: 't' + fields: KV[] + lang?: UnicodeLanguageId +} +export interface PuExtension extends Extension { + type: 'x' + value: string +} + +export interface OtherExtension extends Extension { + type: + | 'a' + | 'b' + | 'c' + | 'd' + | 'e' + | 'f' + | 'g' + | 'h' + | 'i' + | 'j' + | 'k' + | 'l' + | 'm' + | 'n' + | 'o' + | 'p' + | 'q' + | 'r' + | 's' + | 'v' + | 'w' + | 'y' + | 'z' + value: string +} + export interface UnicodeLanguageId { lang: string script?: string @@ -63,6 +121,10 @@ type AlphaNumber = TupleToUnion< Concat, UnionToTuple> > +type OtherExtensions = TupleToUnion< + Concat, UnionToTuple> +> + export type CheckRange< T extends unknown[], Indexes extends number[], @@ -87,6 +149,15 @@ export const localeErrors = /* @__PURE__ */ { 6: 'malformed unicode region subtag', 7: 'unicode region subtag requires 2 alphabet lower characters or 3 digits', 8: 'duplicate unicode variant subtag', + 9: 'malformed unicode extension', + 10: 'missing tvalue for tkey', + 11: 'malformed transformed extension', + 12: 'malformed private use extension', + 13: 'There can only be 1 -u- extension', + 14: 'There can only be 1 -t- extension', + 15: 'There can only be 1 -x- extension', + 16: 'Malformed extension type', + 17: 'There can only be 1 -${type}- extension', 1024: 'Unexpected error', } as const @@ -95,16 +166,25 @@ export const localeErrors = /* @__PURE__ */ { * https://unicode.org/reports/tr35/#unicode_language_id */ export type ParseUnicodeLanguageId< - T extends string, + Chunks extends string | unknown[], ErrorMsg extends Record = typeof localeErrors, - S extends unknown[] = Split, - Lang extends [string, number] = ParseLangSubtag>, - Rest1 extends unknown[] = Shift, - Script extends [string, number] = ParseScriptSubtag>, - Rest2 extends unknown[] = Shift, - Region extends [string, number] = ParseRegionSubtag>, - Rest3 extends unknown[] = Shift, - Variants extends [string[], number | never] = ParseVariantsSubtag< + Chars extends unknown[] = Chunks extends string ? Split : Chunks, + Lang extends [string, number, unknown[]] = ParseLangSubtag< + First, + Chars + >, + Rest1 extends unknown[] = Lang[2], + Script extends [string, number, unknown[]] = ParseScriptSubtag< + First, + Rest1 + >, + Rest2 extends unknown[] = Script[2], + Region extends [string, number, unknown[]] = ParseRegionSubtag< + First, + Rest2 + >, + Rest3 extends unknown[] = Region[2], + Variants extends [string[], number | never, unknown[]] = ParseVariantsSubtag< Rest3 >, Errors extends unknown[] = Filter<[ @@ -113,6 +193,7 @@ export type ParseUnicodeLanguageId< ErrorMsg[Region[1]], ErrorMsg[Variants[1]], ], never>, + RestChars = Variants[2], > = [ { lang: Lang[0] @@ -121,6 +202,7 @@ export type ParseUnicodeLanguageId< variants: Variants[0] }, Length extends 0 ? never : Errors, + RestChars, ] /** @@ -129,17 +211,18 @@ export type ParseUnicodeLanguageId< */ // deno-fmt-ignore export type ParseLangSubtag< - T, - R extends [string, number] = IsNever extends true - ? [never, 1] // missing - : T extends '' - ? [never, 1] // missing - : T extends 'root' - ? ['root', never] // 'root' is special case - : T extends string - ? ParseUnicodeLanguageSubtag + Chunk, + RestChunks extends unknown[] = [], + Result extends [string, number, unknown[]] = IsNever extends true + ? [never, 1, RestChunks] // missing + : Chunk extends '' + ? [never, 1, RestChunks] // missing + : Chunk extends 'root' + ? ['root', never, RestChunks] // 'root' is special case + : Chunk extends string + ? ParseUnicodeLanguageSubtag : never // unexpected -> = R +> = Result /** * parse unicode language subtag (EBNF: = alpha{2,3} | alpha{5,8};) @@ -148,13 +231,14 @@ export type ParseLangSubtag< // TODO: Check if the language subtag is in CLDR // deno-fmt-ignore export type ParseUnicodeLanguageSubtag< - T extends string, - Chars extends unknown[] = StringToArray, + Chunk extends string, + RestChunks extends unknown[] = [], + Chars extends unknown[] = StringToArray, > = CheckRange extends true ? Includes, false> extends true // check if all chars are alphabets - ? [never, 2] // malformed - : [T, never] - : [never, 3] // require characters length + ? [never, 2, RestChunks] // malformed + : [Chunk, never, Shift] + : [never, 3, RestChunks] // require characters length /** * parse unicode script subtag @@ -162,15 +246,16 @@ export type ParseUnicodeLanguageSubtag< */ // deno-fmt-ignore export type ParseScriptSubtag< - T, - R extends [string, number] = IsNever extends true - ? [never, never] // missing - : T extends '' - ? [never, never] // missing - : T extends string - ? ParseUnicodeScriptSubtag + Chunk, + RestChunks extends unknown[] = [], + Result extends [string, number, unknown[]] = IsNever extends true + ? [never, never, RestChunks] // missing + : Chunk extends '' + ? [never, never, RestChunks] // missing + : Chunk extends string + ? ParseUnicodeScriptSubtag : never // unexpected -> = R +> = Result /** * paser unicode script subtag (EBNF: = alpha{4};) @@ -179,13 +264,16 @@ export type ParseScriptSubtag< // TODO: Check if the script subtag is in CLDR // deno-fmt-ignore export type ParseUnicodeScriptSubtag< -T extends string, -Chars extends unknown[] = StringToArray, + Chunk extends string, + RestChunks extends unknown[] = [], + Chars extends unknown[] = StringToArray, > = CheckRange extends true ? Includes, false> extends true // check if all chars are alphabets - ? [never, 4] // malformed - : [T, never] - : [never, 5] // require characters length + ? [never, 4, RestChunks] // malformed + : [Chunk, never, Shift] + : Length extends 0 + ? [never, 5, RestChunks] // require characters length + : [never, never, RestChunks] // through /** * parse unicode region subtag @@ -193,15 +281,16 @@ Chars extends unknown[] = StringToArray, */ // deno-fmt-ignore export type ParseRegionSubtag< - T, - R extends [string, number] = IsNever extends true - ? [never, never] // missing - : T extends '' - ? [never, never] // missing - : T extends string - ? ParseUnicodeRegionSubtag + Chunk, + RestChunks extends unknown[] = [], + Result extends [string, number, unknown[]] = IsNever extends true + ? [never, never, RestChunks] // missing + : Chunk extends '' + ? [never, never, RestChunks] // missing + : Chunk extends string + ? ParseUnicodeRegionSubtag : never, // unexpected -> = R +> = Result /** * parse unicode region subtag (= (alpha{2} | digit{3}) ;) @@ -210,56 +299,63 @@ export type ParseRegionSubtag< // TODO: Check if the region subtag is in CLDR // deno-fmt-ignore export type ParseUnicodeRegionSubtag< - T extends string, - Chars extends unknown[] = StringToArray, + Chunk extends string, + RestChunks extends unknown[] = [], + Chars extends unknown[] = StringToArray, HasAlphabetsOnly = All, true>, HasDigitsOnly = All, true>, > = CheckRange extends true ? Length extends 2 ? HasAlphabetsOnly extends true - ? [T, never] + ? [Chunk, never, Shift] : HasDigitsOnly extends true - ? [never, 7] // require characters length - : [never, 6] // malformed + ? ThroughErrorWithChunks // require characters length + : ThroughErrorWithChunks // malformed : Length extends 3 ? HasDigitsOnly extends true - ? [T, never] + ? [Chunk, never, Shift] : HasAlphabetsOnly extends true - ? [never, 7] // require characters length - : [never, 6] // malformed - : [never, 7] // require characters length - : [never, 7] // require characters length + ? ThroughErrorWithChunks // require characters length + : ThroughErrorWithChunks // malformed + : [never, 7, RestChunks] // require characters length + : ThroughErrorWithChunks // require characters length + +type ThroughErrorWithChunks = + Length extends 0 ? Result : [never, never, Chunks] /** * parse unicode variant subtag * https://unicode.org/reports/tr35/#unicode_variant_subtag */ export type ParseVariantsSubtag< - T extends unknown[], - R extends [string[], number | never] = _ParseVariantsSubtag, -> = R + Chunks extends unknown[], + Result extends [string[], number | never, unknown[]] = _ParseVariantsSubtag< + Chunks + >, +> = Result // deno-fmt-ignore type _ParseVariantsSubtag< - T extends unknown[] = [], + Chunks extends unknown[] = [], Accumrator extends [string[], number | never] = [[], never], - HasVariants = Length extends 0 ? false : true, - Target = First, + HasVariants = Length extends 0 ? false : true, + Target = First, Variant extends string = HasVariants extends true ? Target extends string ? Target : never : never, VariantSubTag = ParseUnicodeVariantsSubtag extends [infer Tag, never] ? Tag : never, - Rest extends unknown[] = Shift, + Rest extends unknown[] = Shift, Duplicate = IsNever extends false ? Includes extends true ? true : false : false, VariantStr extends string = VariantSubTag extends string ? VariantSubTag : never, + RestChunks = IsNever extends true ? Chunks : Rest, > = IsNever extends false - ? [[...Accumrator[0]], Accumrator[1]] + ? [[...Accumrator[0]], Accumrator[1], RestChunks] : Duplicate extends true - ? [[...Accumrator[0]], 8] + ? [[...Accumrator[0]], 8, RestChunks] : IsNever extends true - ? [[...Accumrator[0]], never] + ? [[...Accumrator[0]], never, RestChunks] : _ParseVariantsSubtag], Accumrator[1]]> /** @@ -268,70 +364,523 @@ type _ParseVariantsSubtag< */ // deno-fmt-ignore type ParseUnicodeVariantsSubtag< - T extends string, - Chars extends unknown[] = StringToArray, + Chunk extends string, + Chars extends unknown[] = StringToArray, FirstChar = First, RemainChars extends unknown[]= Shift, > = Length extends 3 ? All, true> extends true // check digit at first char ? All, true> extends true// check alphanum at remain chars - ? [T, never] + ? [Chunk, never] : [never, never] // ignore : [never, never] // ignore : Length extends 4 ? [never, never] // ignore : CheckRange extends true ? All, true> extends true// capture alphanum - ? [T, never] + ? [Chunk, never] : [never, never] // ignore : [never, never] // ignore -export type KV = [string, string] | [string] +// TODO: +type ParseUnicodeLocaleId = true -export interface Extension { - type: string -} +// TODO: +/** + * parse unicode locale extensions + * https://unicode.org/reports/tr35/#extensions + * + * = unicode_locale_extensions + * | transformed_extensions + * | other_extensions ; + */ +type ParseUnicodeExtensions< + Chunks extends unknown[], + Extensions extends UnicodeLocaleId['extensions'] = [], + ResultExtensions extends [Omit, number, unknown[]] = + _ParseUnicodeExtensions< + Chunks, + Extensions + >, + Result extends [Omit, number, unknown[]] = + Length extends 0 ? [{ extensions: [] }, never, Chunks] + : IsNever extends false + ? [{ extensions: [] }, ResultExtensions[1], Chunks] + : [ResultExtensions[0], never, ResultExtensions[2]], +> = Result -export interface UnicodeExtension extends Extension { - type: 'u' - keywords: KV[] - attributes: string[] -} +// type p1 = ParseUnicodeExtensions<['x', '1234']> -export interface TransformedExtension extends Extension { - type: 't' - fields: KV[] - lang?: UnicodeLanguageId -} -export interface PuExtension extends Extension { - type: 'x' - value: string -} +type _ParseUnicodeExtensions< + Chunks extends unknown[], + Extensions extends UnicodeLocaleId['extensions'] = [], + ExistPuExtension extends PuExtension = never, + ExistOtherExtensions extends unknown[] = [], + Chunk = First, + Type extends string = Chunk extends string ? Chunk : never, + RestChunks extends unknown[] = Shift, + // UnicodeExtension = Includes<['u', 'U'], Type> extends true, + // ? ParseUnicodeExtension + // : never, + // TransformedExtension = Includes<['t', 'T'], Type> extends true + // ? ParseTransformedExtension + // : never, -export interface OtherExtension extends Extension { - type: - | 'a' - | 'b' - | 'c' - | 'd' - | 'e' - | 'f' - | 'g' - | 'h' - | 'i' - | 'j' - | 'k' - | 'l' - | 'm' - | 'n' - | 'o' - | 'p' - | 'q' - | 'r' - | 's' - | 'v' - | 'w' - | 'y' - | 'z' - value: string -} + // parse for PuExtension + ResultParsePu extends [PuExtension, number, unknown[]] = + _ParseUnicodeExtensionsPu< + RestChunks, + Type, + ExistPuExtension + >, + _ExtensionsPu extends UnicodeLocaleId['extensions'] = Push< + Extensions, + ResultParsePu[0] + >, /*[ + ...(ResultParsePu[1] extends number ? Extensions + : Push), + ], + */ + // parse for OtherExtension + /* + ResultParseOther extends [OtherExtension, number, unknown[]] = + _ParseUnicodeExtensionsOther< + [...ResultParsePu[2]], // rest chunks + Type, + ExistOtherExtensions + >, + _ExtensionsOther extends UnicodeLocaleId['extensions'] = + ResultParseOther[1] extends number ? [..._ExtensionsPu] + : [...Push<_ExtensionsPu, ResultParseOther[0]>], + NextExistOtherExtensions extends unknown[] = ResultParseOther[0] extends + OtherExtension ? [...Push] + : [...ExistOtherExtensions], + */ + // check error + Error extends number = ResultParsePu[1] extends number ? ResultParsePu[1] + // : ResultParseOther[1] extends number ? ResultParseOther[1] + : never, + // tweak shared chunks for next parsing + NextChunks extends unknown[] = [...ResultParsePu[2]], // [...ResultParseOther[2]], + // tweak extensions + NextExtensions extends UnicodeLocaleId['extensions'] = _ExtensionsPu, // _ExtensionsOther, + NextExistPuExtension extends PuExtension = ResultParsePu[0], +> = IsNever extends false ? [never, Error, Chunks] + : Length extends 0 ? [{ extensions: Extensions }, never, Chunks] + : Length extends 0 + ? [{ extensions: NextExtensions }, never, NextChunks] + : _ParseUnicodeExtensions< + NextChunks, + NextExtensions, + NextExistPuExtension + > // ResultParsePu[0] +// NextExistOtherExtensions + +// type pp1 = _ParseUnicodeExtensions<['x', '1234']> + +type _ParseUnicodeExtensionsPu< + Chunks extends unknown[], + Type extends string, + ExistPuExtension extends PuExtension = never, + ResultParsePuExtension extends unknown[] = + CheckExtensionType extends true + ? ParsePuExtension<[...Chunks]> + : never, + _PuExtension extends PuExtension = ResultParsePuExtension[0] extends + PuExtension ? ResultParsePuExtension[0] + : never, + RestChunks extends unknown[] = ResultParsePuExtension[2] extends unknown[] + ? ResultParsePuExtension[2] + : Chunks, + Error extends number = IsNever extends false ? 14 + : ResultParsePuExtension[1] extends number ? ResultParsePuExtension[1] + : never, + Result extends [PuExtension, number, unknown[]] = [ + IsNever extends true ? _PuExtension : never, + Error, + RestChunks, + ], +> = Result + +// type _pu0 = _ParseUnicodeExtensionsPu< +// ['1234'], +// 'x' +// > +// type _pu1 = _ParseUnicodeExtensionsPu< +// ['1234'], +// 'x', +// { type: 'x'; value: '111' } +// > + +type _ParseUnicodeExtensionsOther< + Chunks extends unknown[], + Type extends string, + ExistOtherExtensions extends unknown[] = never, + MalformedError extends number = + Includes, Type> extends false ? 16 + : never, + Error extends number = MalformedError extends number ? MalformedError + : Includes extends true ? 17 + : never, + ResultParseOtherExtension extends [string, unknown[]] = IsNever extends + true ? ParseOtherExtension<[...Chunks]> + : [never, Chunks], + RestChunks extends unknown[] = ResultParseOtherExtension[1] extends unknown[] + ? ResultParseOtherExtension[1] + : Chunks, + Result extends [OtherExtension, number, unknown[]] = [ + ResultParseOtherExtension[0] extends string + ? { type: 'a'; value: ResultParseOtherExtension[0] } + : never, + Error, + RestChunks, + ], +> = Result + +// type _ou1 = _ParseUnicodeExtensionsOther<['abc'], 'z'> + +type CheckExtensionType< + Type extends string, + Ext extends string[], + Chars extends unknown[] = StringToArray, +> = Length extends 1 ? Includes extends true ? true + : false + : false + +/** + * parse unicode locale extension + * https://unicode.org/reports/tr35/#unicode_locale_extensions + * + * = ((sep keyword)+ | (sep attribute)+ (sep keyword)*) ; + */ +// deno-fmt-ignore +export type ParseUnicodeExtension< + Chunks extends unknown[], + Sep extends string = '-', + ResultFirstKeyword extends unknown[] = CollectFirstKeywords, + FirstKeywolds extends unknown[] = ResultFirstKeyword[0] extends unknown[] ? ResultFirstKeyword[0] : never, + FirstRestChunks extends unknown[] = ResultFirstKeyword[1] extends unknown[] ? ResultFirstKeyword[1] : Chunks, + ResultAttribute extends [unknown[], unknown[]] = ParseAttribute, + Attributes extends unknown[] = ResultAttribute[0], + RestAttributeChunks extends unknown[] = ResultAttribute[1] extends unknown[] ? ResultAttribute[1] : never, + ResultKeyword extends unknown[] = ParseKeyword, + _Keywords extends unknown[] = Push<[], ResultKeyword[0]>, + Keywords extends unknown[] = Push<_Keywords, ResultKeyword[1]>, + RestChunks extends unknown[] = ResultKeyword[2] extends unknown[] ? ResultKeyword[2] : never, +> = Length extends 0 + ? IsNever extends false + ? Length extends 0 + ? Length extends 0 + ? [never, 9] // malformed + : [{ type: 'u'; keywords: Keywords; attributes: Attributes }, never, RestChunks] + : [{ type: 'u'; keywords: Keywords; attributes: Attributes }, never, RestChunks] + : [{ type: 'u'; keywords: []; attributes: Attributes }, never, ResultKeyword[1]] + : [{ type: 'u'; keywords: FirstKeywolds; attributes: [] }, never, FirstRestChunks] + +type t0 = ParseUnicodeExtension<['c']> +type t1 = ParseUnicodeExtension<['co', 'standard']> +type t2 = ParseUnicodeExtension<['foo', 'bar', 'co', 'standard']> + +// deno-fmt-ignore +export type CollectFirstKeywords< + Chunks extends unknown[], + Sep extends string = '-', + Keywords extends unknown[] = [], + ResultKeyword extends unknown[] = ParseKeyword, + RestChunks extends unknown[] = ResultKeyword[2] extends unknown[] ? ResultKeyword[2] : never, + _Keywords1 extends unknown[] = Push<[], ResultKeyword[0]>, + _Keywords2 extends unknown[] = Push<_Keywords1, ResultKeyword[1]>, +> = Length extends 0 + ? Length extends 0 + ? [never, Chunks] + : [Keywords, Chunks] + : IsNever extends true + ? [Keywords, Chunks] + : CollectFirstKeywords + +// type c0 = CollectFirstKeywords<['c']> +// type c1 = CollectFirstKeywords<['co', 'standard', 'x']> +// type c2 = CollectFirstKeywords<['co', 'standard', 'r111', 'u']> +// type c4 = CollectFirstKeywords<['foo', 'bar', 'co', 'standard']> + +/** + * parse attribute at unicode locale extension generally + * `attribute` at https://unicode.org/reports/tr35/#Unicode_locale_identifier + * + * (= alphanum{3,8} ;) + */ +// deno-fmt-ignore +export type ParseAttribute< + Chunks extends unknown[], + Attributes extends unknown[] = [], + RestChunks extends unknown[] = Shift, + Chunk extends string = Chunks[0] extends string ? Chunks[0] : never, + ChunkChars extends unknown[] = StringToArray, +> = Length extends 0 + ? [Attributes, RestChunks] + : Chunk extends string + ? CheckRange extends true // check attribute length + ? All, true> extends true // check attribute characters + ? ParseAttribute< + RestChunks, + [...Push] + > + : [Attributes, Chunks] + : [Attributes, Chunks] + : [Attributes, Chunks] + +// type pa1 = ParseAttribute<['foo', 'bar', 'co', 'standard']> +// type pa2 = ParseAttribute<['foo', 'bar']> +// type pa3 = ParseAttribute<['co', 'standard']> +// type pa4 = ParseAttribute<['c']> + +/** + * parse keyword at unicode locale extension generally + * `keyword` at https://unicode.org/reports/tr35/#Unicode_locale_identifier + * + * (= key (sep type)? ;) + */ +// deno-fmt-ignore +export type ParseKeyword< + Chunks extends unknown[], + Sep extends string = '-', + Key = ParseKeywordKey, + Rest extends unknown[] = Shift, + ResultValue extends unknown[] = ParseKeywordValue, +> = IsNever extends true + ? [never, Chunks] + : [Key, ResultValue[0], ResultValue[1]] + +// type k = ParseKeyword<['']> +// type k0 = ParseKeyword<['c']> +// type k1 = ParseKeyword<['co', 'standard', 'x']> +// type k2 = ParseKeyword<['co', 'standard', 'r111', 'u']> +// type k3 = ParseKeyword<['co', 'standard']> + +/** + * parse keyword key at unicode locale extension generally + * `key` at https://unicode.org/reports/tr35/#Unicode_locale_identifier + * + * (= alphanum alpha ;) + */ +// deno-fmt-ignore +type ParseKeywordKey< + Chunks extends unknown[], + _Chunk = First, + Chunk extends string = _Chunk extends string ? _Chunk : never, + ChunkChars extends unknown[] = StringToArray, + Key1 extends string = ChunkChars[0] extends string ? ChunkChars[0] : never, + Key2 extends string = ChunkChars[1] extends string ? ChunkChars[1] : never, +> = Chunk extends string + ? Length extends 2 + ? All, AlphaNumber>, true> extends true + ? All, Alpha>, true> extends true + ? Chunk + : never + : never + : never + : never + +// deno-fmt-ignore +type ParseKeywordValue< + Chunks extends unknown[], + Sep extends string = '-', + ResultKeywordType extends [unknown[], unknown[]] = ParseKeywordType, +> = Length extends 0 + ? ['', ResultKeywordType[1]] + : [Join, ResultKeywordType[1]] + +/** + * parse type on keyword at unicode locale extension generally + * `type` at https://unicode.org/reports/tr35/#Unicode_locale_identifier + * + * (= alphanum{3,8} (sep alphanum{3,8})* ;) + */ +// deno-fmt-ignore +type ParseKeywordType< + Chunks extends unknown[], + Types extends unknown[] = [], + Chunk extends string = Chunks[0] extends string ? Chunks[0] : never, + ChunkChars extends unknown[] = StringToArray, + ExitReturn = [Types, Chunks], +> = Length extends 0 + ? ExitReturn + : Chunk extends string + ? CheckRange extends true // check type length + ? All, true> extends true // check type characters + ? ParseKeywordType, [...Push]> + : ExitReturn + : ExitReturn + : ExitReturn + +/** + * parse transformed extension + * https://unicode.org/reports/tr35/#transformed_extensions + * + * = sep [tT] ((sep tlang (sep tfield)*) | (sep tfield)+) ; + */ +// deno-fmt-ignore +export type ParseTransformedExtension< + Chunks extends unknown[], + /* Excessive stack depth comparing types ... + ResultLangId extends [UnicodeLanguageId, number, unknown[]] = + ParseUnicodeLanguageId, + */ + ResultLangId extends unknown[] = ParseUnicodeLanguageId< + Chunks + >, + LangParseError extends number = ResultLangId[1] extends number ? ResultLangId[1] : never, + RestChunks extends unknown[] = ResultLangId[2] extends unknown[] + ? ResultLangId[2] + : never, + ResultFields extends unknown[] = ParseTransformedExtensionFields, + Fields extends unknown[] = ResultFields[0] extends unknown[] ? ResultFields[0] : never, + TransformedParseError = ResultFields[1] extends number ? ResultFields[1] : never, + NextChunks extends unknown[] = IsNever extends false + ? Chunks + : IsNever extends false + ? RestChunks + : ResultFields[2] extends unknown[] + ? ResultFields[2] + : Chunks +> = IsNever extends false + ? [{ type: 't', lang: ResultLangId[0], fields: ResultFields[0] }, LangParseError, NextChunks] + : IsNever extends false + ? [{ type: 't', lang: ResultLangId[0], fields: ResultFields[0] }, TransformedParseError, NextChunks] + : Length extends 0 + ? [never, 11, Chunks] // malformed + : [{ type: 't', lang: ResultLangId[0], fields: ResultFields[0] }, never, NextChunks] + +// type pt1 = ParseTransformedExtension< +// ['en', 'Kana', 'US', 'jauer', 'h0', 'hybrid'] +// > +// type ll1 = ParseUnicodeLanguageId< +// ['en', 'Kana', 'US', 'jauer', 'h0', 'hybrid'] +// > +// type ll2 = ParseTransformedExtensionFields<['h0', 'hybrid']> + +/** + * parse `tfield` at unicode transformed extension + * https://unicode.org/reports/tr35/#transformed_extensions + */ +// deno-fmt-ignore +type ParseTransformedExtensionFields< + Chunks extends unknown[], + Sep extends string = '-', + Accumrator extends [unknown[], number, unknown[]] = [[], never, []], + Key extends string = Chunks[0] extends string ? Chunks[0] : never, + KeyChars extends unknown[] = StringToArray, + ResultValue extends [unknown[], unknown[]] = ParseTransformedExtensionFieldsValue< + Shift + >, + FieldsReturn = [Accumrator[0], Accumrator[1], Chunks], +> = Length extends 0 + ? FieldsReturn + : CheckRange extends true // check `tfield` length + ? All, true> extends true // check `tfield` characters + ? All, true> extends true // check `tfield` characters + ? Length extends 0 + ? [never, 10, Chunks] // missing + : [Push]>, Accumrator[1], ResultValue[1]] + : FieldsReturn + : FieldsReturn + : FieldsReturn + +// type ppt1 = ParseTransformedExtensionFields< +// ['h0', 'hybrid'] +// > +// type ppt2 = ParseTransformedExtensionFields< +// ['h0'] +// > + +// deno-fmt-ignore +type ParseTransformedExtensionFieldsValue< + Chunks extends unknown[], + Value extends unknown[] = [], + Chunk extends string = Chunks[0] extends string ? Chunks[0] : never, + ChunkChars extends unknown[] = StringToArray, + ExitReturn = [Value, Chunks], +> = Length extends 0 + ? ExitReturn + : CheckRange extends true // check `tfield` value length + ? All, true> extends true // check `tfield` value characters + ? ParseTransformedExtensionFieldsValue< + Shift, + [...Push] + > + : ExitReturn + : ExitReturn + +/** + * parse private use extensions + * https://unicode.org/reports/tr35/#pu_extensions + * + * = sep [xX] (sep alphanum{1,8})+ ; + */ +// deno-fmt-ignore +export type ParsePuExtension< + Chunks extends unknown[], + Sep extends string = '-', + ResultExts extends [unknown[], unknown[]] = _ParsePuExtension< + Chunks + >, + // NOTE: workaround for `Excessive stack depth comparing types` + ResultExts0 extends unknown[] = ResultExts[0] extends unknown[] ? ResultExts[0] : never, + ResultExts1 extends unknown[] = ResultExts[1] extends unknown[] ? ResultExts[1] : never, + Result extends [PuExtension, number, unknown[]] = Length extends 0 + ? [never, 12, ResultExts1] + : [{ type: 'x'; value: Join }, never, ResultExts1], +> = Result + +export type _ParsePuExtension< + Chunks extends unknown[], + Exts extends unknown[] = [], + Chunk extends string = Chunks[0] extends string ? Chunks[0] : never, + ChunkChars extends unknown[] = StringToArray, + ExitReturn = [Exts, Chunks], +> = Length extends 0 ? ExitReturn + : CheckRange extends true // check value length + ? All, true> extends true // check value characters + ? _ParsePuExtension< + Shift, + [...Push] + > + : ExitReturn + : ExitReturn + +/** + * parse other extension + * https://unicode.org/reports/tr35/#other_extensions + * + * = sep [alphanum-[tTuUxX]] + * (sep alphanum{2,8})+ ; + */ +// deno-fmt-ignore +export type ParseOtherExtension< + Chunks extends unknown[], + Sep extends string = '-', + ResultExts extends [unknown[], unknown[]] = _ParseOtherExtension< + Chunks + >, + Result extends [string, unknown[]] = Length extends 0 + ? ['', ResultExts[1]] + : [Join, ResultExts[1]] +> = Result + +// type o1 = ParseOtherExtension<['foo', 'bar', 'co', 'standard']> + +type _ParseOtherExtension< + Chunks extends unknown[], + Exts extends unknown[] = [], + Chunk extends string = Chunks[0] extends string ? Chunks[0] : never, + ChunkChars extends unknown[] = StringToArray, + ExitReturn = [Exts, Chunks], +> = Length extends 0 ? ExitReturn + : CheckRange extends true // check value length + ? All, true> extends true // check value characters + ? _ParseOtherExtension< + Shift, + [...Push] + > + : ExitReturn + : ExitReturn diff --git a/src/types.ts b/src/types.ts index 3d533f7..85e4c20 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,6 +5,10 @@ export type Split = string extends S ? [A, ...(B extends '' ? [] : Split)] : SEP extends '' ? [] : [S] +export type Join = T extends + [infer F, ...infer R] ? R['length'] extends 0 ? `${F & string}` + : `${F & string}${U}${Join}` + : never export type Shift = T extends [unknown, ...infer U] ? U : never export type First = T extends [infer A, ...infer rest] ? A