Last active
November 12, 2020 04:50
-
-
Save Lucifier129/251fb9b8b306565d9525f2f673a5cefd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type ParseResult = { | |
success: boolean, | |
data: any, | |
source: string | |
} | |
type ParseFailed = { | |
suceess: false, | |
data: any, | |
source: string | |
} | |
type ParseSuccess = { | |
success: true, | |
data: any, | |
source: string | |
} | |
type TrimLeft<T extends string> = | |
T extends `${' ' | '\n'}${infer R}` | |
? TrimLeft<R> | |
: T | |
type TrimRight<T extends string> = | |
T extends `${infer R}${' ' | '\n'}` | |
? TrimRight<R> | |
: T | |
type Trim<T extends string> = | |
TrimLeft<T> extends `${infer R}` | |
? TrimRight<R> | |
: T | |
type LowerAlphabet = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | |
type UpperAlphabet = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" | |
type Alphabet = LowerAlphabet | UpperAlphabet | |
type Digit = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "0" | |
type Letter = Alphabet | Digit | '_' | '-' | '$' | |
type IsAllLetter<T extends string> = | |
T extends Letter ? true : | |
T extends `${Letter}${infer R}` | |
? R extends '' ? true : IsAllLetter<R> | |
: false | |
type IsIdentifier<T extends string> = | |
T extends `${Alphabet}${infer R}` | |
? R extends '' ? true : IsAllLetter<R> | |
: false | |
type AttrObj<AttrName extends string, AttrValue, R> = | |
IsIdentifier<AttrName> extends true | |
? { | |
success: true, | |
data: { [key in AttrName]: AttrValue }, | |
source: R | |
} | |
: { | |
success: false, | |
data: { | |
reason: 'invalid attribute name', | |
attrName: AttrName | |
}, | |
source: R | |
} | |
type ParseValuedAttr<T extends string> = | |
T extends `${infer AttrName}="${infer AttrValue}"${infer R}` | |
? AttrObj<AttrName, AttrValue, R> | |
: ParseFailed | |
type ParseEmptyValuedAttr<T extends string> = | |
T extends `${infer AttrName}=""${infer R}` | |
? AttrObj<AttrName, '', R> | |
: ParseFailed | |
type ParseBoolAttr<T extends string> = | |
T extends `${infer AttrName} ${infer R}` ? AttrObj<AttrName, true, R> : | |
T extends `${infer AttrName} ` ? AttrObj<AttrName, true, ''> : | |
T extends `${infer AttrName}` ? AttrObj<AttrName, true, ''> : | |
ParseFailed | |
type ParseAttr<T extends string> = | |
TrimLeft<T> extends `${infer T}` ? | |
ParseValuedAttr<T> extends ParseSuccess ? ParseValuedAttr<T> | |
: ParseEmptyValuedAttr<T> extends ParseSuccess ? ParseEmptyValuedAttr<T> | |
: ParseBoolAttr<T> extends ParseSuccess ? ParseBoolAttr<T> | |
: { | |
success: false, | |
data: { | |
reason: 'parse attr failed, none of valued-attr, empty-valued-attr, bool-attr is matched', | |
parseValuedAttr: ParseValuedAttr<T> | |
parseEmptyValuedAttr: ParseEmptyValuedAttr<T> | |
parseBoolAttr: ParseBoolAttr<T> | |
}, | |
source: T | |
} | |
: ParseFailed | |
type ParseAttrs<T extends string> = | |
TrimLeft<T> extends `${infer R0}` ? | |
// empty text | |
R0 extends '' | |
? { | |
success: true, | |
data: {}, | |
source: '' | |
} : | |
// match attr | |
ParseAttr<R0> extends { | |
success: true, | |
data: infer AttrsData, | |
source: `${infer R1}` | |
} ? | |
// try match rest attrs | |
ParseAttrs<R1> extends { | |
success: true, | |
data: infer RestAttrsData, | |
source: `${infer R2}` | |
} ? | |
// has rest attrs | |
// all attrs are consumed | |
Trim<R2> extends '' | |
? { | |
success: true, | |
data: { | |
[key in keyof (AttrsData & RestAttrsData)]: (AttrsData & RestAttrsData)[key] | |
}, | |
source: R2 | |
} | |
// some attrs failed to parse | |
: { | |
success: false, | |
data: { | |
reason: 'some attrs failed to parse', | |
props: AttrsData & RestAttrsData, | |
}, | |
source: R2 | |
} : | |
// no rest attrs | |
// source is empty, means all attrs are matched | |
Trim<R1> extends '' | |
? { | |
success: true, | |
data: AttrsData, | |
source: R1 | |
} | |
// source is not empty, means some attrs are not matched | |
: ParseAttrs<R1> | |
// no attrs match | |
: { | |
success: false, | |
data: { | |
reason: 'parse attrs failed' | |
}, | |
source: T | |
} : | |
ParseFailed | |
type ParseEmptyAttrsOpenTag<T extends string> = | |
TrimLeft<T> extends `${infer T}` | |
? T extends `<${infer TagName}>${infer R0}` | |
? IsIdentifier<TagName> extends true ? | |
{ | |
success: true, | |
data: { | |
tagName: TagName, | |
props: {} | |
}, | |
source: R0 | |
} : { | |
success: false, | |
data: { | |
reason: 'invalid tagname', | |
tagName: TagName | |
}, | |
source: R0 | |
} | |
: { | |
success: false, | |
data: { | |
reason: 'parse empty-attrs-open-tag failed' | |
}, | |
source: T | |
} | |
: T | |
type ParseNonEmptyAttrsOpenTag<T extends string> = | |
TrimLeft<T> extends `${infer T}` ? | |
T extends `<${infer TagName} ${infer Attrs}>${infer R0}` ? | |
IsIdentifier<TagName> extends true ? | |
// valid tagname, try attrs | |
ParseAttrs<Attrs> extends { | |
success: true, | |
data: infer Props, | |
source: string | |
} | |
// valid attrs, output | |
? { | |
success: true, | |
data: { | |
tagName: TagName | |
props: Props | |
}, | |
source: R0 | |
} | |
// invalid attrs, show error info | |
: ParseAttrs<Attrs> | |
// invalid tagname | |
: { | |
success: false, | |
data: { | |
reason: 'invalid tagname', | |
tagName: TagName | |
}, | |
source: R0 | |
} | |
// match failed | |
: { | |
success: false, | |
data: { | |
reason: 'parse non-empty-attrs-open-tag failed' | |
}, | |
source: T | |
} | |
: ParseFailed | |
type ParseOpenTag<T extends string> = | |
TrimLeft<T> extends `${infer T}` | |
? ParseNonEmptyAttrsOpenTag<T> extends ParseSuccess ? ParseNonEmptyAttrsOpenTag<T> | |
: ParseEmptyAttrsOpenTag<T> extends ParseSuccess ? ParseEmptyAttrsOpenTag<T> | |
: { | |
success: false, | |
data: { | |
reason: 'parse open-tag failed', | |
parseNonEmptyAttrsOpenTag: ParseNonEmptyAttrsOpenTag<T>, | |
parseEmptyAttrsOpenTag: ParseEmptyAttrsOpenTag<T> | |
}, | |
source: T | |
} | |
: ParseFailed | |
type ParseChildren<T extends string> = ParseNode<T> extends ParseSuccess ? ParseNode<T> : { | |
success: true, | |
data: [], | |
source: T | |
} | |
type ParseCloseTag<TagName extends string, Source extends string> = | |
TrimLeft<Source> extends `${infer Source}` | |
? Source extends `</${TagName}>${infer R}` | |
? { | |
success: true, | |
data: { | |
tagName: TagName | |
}, | |
source: R | |
} | |
: { | |
success: false, | |
data: { | |
reason: 'parse close-tag failed', | |
tagName: TagName | |
}, | |
source: Source | |
} | |
: ParseFailed | |
type ParseEmptyAttrsSelfClosingTag<T extends string> = | |
TrimLeft<T> extends `${infer T}` ? | |
T extends `<${infer TagName}/>${infer R0}` ? | |
IsIdentifier<TagName> extends true | |
? { | |
success: true, | |
data: { | |
tagName: TagName, | |
props: {} | |
}, | |
source: R0 | |
} | |
: { | |
success: false, | |
data: { | |
reason: 'invalid tagname', | |
tagName: TagName | |
}, | |
source: T | |
} | |
: { | |
success: false, | |
data: { | |
reason: 'parse empty-attrs-self-closing-tag failed' | |
}, | |
source: T | |
} | |
: ParseFailed | |
type ParseNonEmptyAttrsSelfClosingTag<T extends string> = | |
TrimLeft<T> extends `${infer T}` ? | |
T extends `<${infer TagName} ${infer Attrs}/>${infer R0}` ? | |
IsIdentifier<TagName> extends true | |
? | |
// valid tagname, try attrs | |
ParseAttrs<Attrs> extends { | |
success: true, | |
data: infer Props, | |
source: string | |
} | |
// valid attrs, output | |
? { | |
success: true, | |
data: { | |
tagName: TagName | |
props: Props | |
}, | |
source: R0 | |
} | |
// invalid attrs, show error info | |
: ParseAttrs<Attrs> | |
: { | |
success: false, | |
data: { | |
reason: 'invalid tagname', | |
tagName: TagName | |
}, | |
source: T | |
} | |
: { | |
success: false, | |
data: { | |
reason: 'parse empty-attrs-self-closing-tag failed' | |
}, | |
source: T | |
} | |
: ParseFailed | |
type ParseSelfClosingTag<T extends string> = | |
TrimLeft<T> extends `${infer T}` | |
? ParseNonEmptyAttrsSelfClosingTag<T> extends ParseSuccess ? ParseNonEmptyAttrsSelfClosingTag<T> | |
: ParseEmptyAttrsSelfClosingTag<T> extends ParseSuccess ? ParseEmptyAttrsSelfClosingTag<T> | |
: { | |
success: false, | |
data: { | |
reason: 'parse self-closing-tag failed', | |
parseNonEmptyAttrsSelfClosingTag: ParseNonEmptyAttrsSelfClosingTag<T>, | |
parseEmptyAttrsSelfClosingTag: ParseEmptyAttrsSelfClosingTag<T> | |
} | |
} | |
: ParseFailed | |
type ParseAnyCloseTag<Source extends string> = | |
TrimLeft<Source> extends `</${infer TagName}>${infer R}` ? | |
IsIdentifier<TagName> extends true | |
? { | |
success: true, | |
data: { | |
tagName: TagName | |
}, | |
source: R | |
} | |
: { | |
success: false, | |
data: { | |
reason: 'invalid close tagname', | |
tagName: TagName | |
}, | |
source: R | |
} | |
: { | |
success: false, | |
data: { | |
reason: 'parse close-tag failed' | |
}, | |
source: Source | |
} | |
type ParseOpenOrCloseTag<T extends string> = | |
TrimLeft<T> extends `${infer T}` ? | |
ParseOpenTag<T> extends ParseSuccess ? ParseOpenTag<T> | |
: ParseAnyCloseTag<T> extends ParseSuccess ? ParseAnyCloseTag<T> | |
: ParseSelfClosingTag<T> extends ParseSuccess ? ParseSelfClosingTag<T> | |
: { | |
success: false, | |
data: { | |
reason: 'parse open or close tag failed' | |
}, | |
source: T | |
} | |
: ParseFailed | |
type ParseText<T extends string> = | |
TrimLeft<T> extends `${infer T}` ? | |
// has < found | |
T extends `${infer Text}<${infer R0}` ? | |
// try open-tag or close-tag | |
ParseOpenOrCloseTag<`<${R0}`> extends ParseSuccess ? | |
// found open or close tag | |
Text extends '' | |
// text is empty | |
? { | |
success: false, | |
data: { | |
reason: 'no text found' | |
}, | |
source: T | |
} | |
// text is not empty | |
: { | |
success: true, | |
data: TrimRight<Text>, | |
source: `<${R0}` | |
} : | |
// no open-tag or close-tag, parse text again | |
ParseText<R0> extends { | |
success: true, | |
data: `${infer RestText}`, | |
source: `${infer R1}` | |
} | |
// found more text | |
? { | |
success: true, | |
data: `${Text}<${RestText}`, | |
source: R1 | |
} | |
// no more text | |
: { | |
success: true, | |
data: `${Text}<`, | |
source: R0 | |
} : | |
// no < found, all source is text | |
Trim<T> extends '' | |
// text is empty | |
? { | |
success: false, | |
data: { | |
reason: 'no text found' | |
}, | |
source: T | |
} | |
// text is not empty | |
: { | |
success: true, | |
data: TrimRight<T>, | |
source: '' | |
} | |
: ParseFailed | |
type ParseEmptyTag<T extends string> = | |
TrimLeft<T> extends `${infer T}` ? | |
// try parse oepn-tag | |
ParseOpenTag<T> extends { | |
success: true, | |
data: { | |
tagName: `${infer TagName}`, | |
props: infer Props | |
}, | |
source: `${infer R0}` | |
} ? | |
// try parse close-tag | |
ParseCloseTag<TagName, R0> extends { | |
success: true, | |
data: any, | |
source: `${infer R1}` | |
} | |
// found open-tag & close-tag | |
? { | |
success: true, | |
data: { | |
tagName: TagName, | |
props: Props | |
}, | |
source: R1 | |
} | |
// failed to match close-tag | |
: { | |
success: false, | |
data: { | |
reason: 'unmatch closed tag', | |
tagName: TagName | |
props: Props, | |
parseCloseTag: ParseCloseTag<TagName, R0> | |
}, | |
source: R0 | |
} | |
: { | |
success: false, | |
data: { | |
reason: 'failed to parse empty tag', | |
parseOpenTag: ParseOpenTag<T> | |
}, | |
source: T | |
} | |
: ParseFailed | |
type ParseNonEmptyTag<T extends string> = | |
TrimLeft<T> extends `${infer T}` ? | |
// try parse oepn-tag | |
ParseOpenTag<T> extends { | |
success: true, | |
data: { | |
tagName: `${infer TagName}`, | |
props: infer Props | |
}, | |
source: `${infer R0}` | |
} ? | |
// try parse children | |
ParseNode<R0> extends { | |
success: true, | |
data: [...infer Children], | |
source: `${infer R1}` | |
} ? | |
// try parse close-tag | |
ParseCloseTag<TagName, R1> extends { | |
success: true, | |
data: any, | |
source: `${infer R2}` | |
} | |
// found open-tag & children & close-tag | |
? { | |
success: true, | |
data: { | |
tagName: TagName, | |
props: Props, | |
children: Children | |
}, | |
source: R2 | |
} | |
// failed to match close-tag | |
: { | |
success: false, | |
data: { | |
reason: 'unmatch closed tag', | |
tagName: TagName | |
props: Props, | |
children: Children, | |
parseCloseTag: ParseCloseTag<TagName, R1> | |
}, | |
source: R1 | |
} | |
// failed to match children | |
: { | |
success: false, | |
data: { | |
reason: 'parse children failed', | |
tagName: TagName | |
props: Props, | |
parseChildren: ParseNode<R0> | |
}, | |
source: R0 | |
} | |
: { | |
success: false, | |
data: { | |
reason: 'failed to parse non-empty tag', | |
parseOpenTag: ParseOpenTag<T> | |
}, | |
source: T | |
} | |
: ParseFailed | |
type ParseNormalTag<T extends string> = | |
TrimLeft<T> extends `${infer T}` ? | |
ParseEmptyTag<T> extends ParseSuccess ? ParseEmptyTag<T> | |
: ParseNonEmptyTag<T> extends ParseSuccess ? ParseNonEmptyTag<T> | |
: { | |
success: false, | |
data: { | |
reason: 'parse normal tag', | |
parseNonEmpty: ParseNonEmptyTag<T> | |
parseEmpty: ParseEmptyTag<T>, | |
}, | |
source: T | |
} | |
: ParseFailed | |
type ParseTag<T extends string> = | |
TrimLeft<T> extends `${infer T}` ? | |
ParseNormalTag<T> extends ParseSuccess ? ParseNormalTag<T> | |
// failed to parse normal tag, then try parse self-closing-tag | |
: ParseSelfClosingTag<T> extends ParseSuccess ? ParseSelfClosingTag<T> | |
: { | |
success: false, | |
data: { | |
reason: 'parse tag failed', | |
parseNormalTag: ParseNormalTag<T>, | |
parseSelfClosingTag: ParseSelfClosingTag<T> | |
}, | |
source: T | |
} | |
: ParseFailed | |
type ParseTagOrText<T extends string> = | |
TrimLeft<T> extends `${infer T}` ? | |
ParseTag<T> extends ParseSuccess ? ParseTag<T> | |
: ParseText<T> extends ParseSuccess ? ParseText<T> | |
: { | |
success: false, | |
data: { | |
reason: 'parse tag or text failed', | |
parseNormalTag: ParseNormalTag<T>, | |
parseSelfClosingTag: ParseSelfClosingTag<T> | |
parseText: ParseText<T> | |
}, | |
source: T | |
} | |
: ParseFailed | |
type ParseNode<T extends string> = | |
TrimLeft<T> extends `${infer T}` ? | |
// source is empty | |
T extends '' ? { | |
success: true, | |
data: [], | |
source: '' | |
} : | |
ParseTagOrText<T> extends { | |
success: true, | |
data: infer Node, | |
source: `${infer R0}` | |
} ? | |
// found tag, try parse siblings | |
ParseNode<R0> extends { | |
success: true, | |
data: [...infer NodeList], | |
source: `${infer R1}` | |
} | |
// has siblings | |
? { | |
success: true, | |
data: [Node, ...NodeList], | |
source: R1 | |
} | |
// has no siblings | |
: { | |
success: true, | |
data: [Node], | |
source: R0 | |
} | |
// no found | |
: { | |
success: false, | |
data: { | |
reason: 'parse node failed', | |
parseNormalTag: ParseNormalTag<T>, | |
parseSelfClosingTag: ParseSelfClosingTag<T> | |
parseText: ParseText<T> | |
}, | |
source: T | |
} | |
: ParseFailed | |
// test empty | |
// { | |
// success: true; | |
// data: []; | |
// source: ''; | |
// } | |
type T0 = ParseNode<''> | |
// test raw text | |
// { | |
// success: true; | |
// data: ["123"]; | |
// source: ""; | |
// } | |
type T1 = ParseNode<' 123 '> | |
// test self-closing-tag | |
// { | |
// success: true; | |
// data: [{ | |
// tagName: "input"; | |
// props: { | |
// type: "radio"; | |
// checked: true; | |
// }; | |
// }]; | |
// source: ""; | |
// } | |
type T2 = ParseNode<`<input type="radio" checked />`> | |
// test normal tag without attributes | |
// { | |
// success: true; | |
// data: [{ | |
// tagName: "div"; | |
// props: {}; | |
// children: ["123"]; | |
// }]; | |
// source: ""; | |
// } | |
type T3 = ParseNode<`<div>123</div>`> | |
// test normal tag with attributes | |
// { | |
// success: true; | |
// data: { | |
// tagName: "div"; | |
// props: { | |
// id: "test"; | |
// class: "container"; | |
// "data-id": "123"; | |
// title: "456"; | |
// }; | |
// children: ["456"]; | |
// }; | |
// source: ""; | |
// } | |
type T4 = ParseNode<`<div id="test" class="container" data-id="123" title="456">456</div>`> | |
// test multiple nodes | |
// { | |
// success: true; | |
// data: [{ | |
// tagName: "input"; | |
// props: { | |
// type: "radio"; | |
// checked: true; | |
// }; | |
// }, { | |
// tagName: "div"; | |
// props: {}; | |
// children: ["123"]; | |
// }, { | |
// tagName: "div"; | |
// props: { | |
// id: "test"; | |
// class: "container"; | |
// "data-id": "123"; | |
// title: "456"; | |
// }; | |
// children: ["456"]; | |
// }]; | |
// source: ""; | |
// } | |
type T5 = ParseNode<` | |
<input type="radio" checked /> | |
<div>123</div> | |
<div id="test" class="container" data-id="123" title="456">456</div> | |
`> | |
// test nest tag | |
// { | |
// success: true; | |
// data: [{ | |
// tagName: "div"; | |
// props: { | |
// class: "parent-0"; | |
// }; | |
// children: [{ | |
// tagName: "div"; | |
// props: { | |
// class: "child-0"; | |
// }; | |
// children: ["text 0"]; | |
// }, { | |
// tagName: "div"; | |
// props: { | |
// class: "child-1"; | |
// }; | |
// children: [...]; | |
// }, { | |
// ...; | |
// }]; | |
// }, { | |
// ...; | |
// }]; | |
// source: ""; | |
// } | |
type T6 = ParseNode<` | |
<div class="parent-0"> | |
<div class="child-0"> | |
text 0 | |
</div> | |
<div class="child-1"> | |
text1 | |
</div> | |
<div class="child-2"> | |
text2 | |
</div> | |
</div> | |
<div class="parent-1"> | |
<div class="child-0"> | |
text 0 | |
</div> | |
<div class="child-1"> | |
text1 | |
</div> | |
<div class="child-2"> | |
text2 | |
</div> | |
</div> | |
`> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
学习了,tql