Skip to content

Instantly share code, notes, and snippets.

@Lucifier129
Last active November 12, 2020 04:50
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Lucifier129/251fb9b8b306565d9525f2f673a5cefd to your computer and use it in GitHub Desktop.
Save Lucifier129/251fb9b8b306565d9525f2f673a5cefd to your computer and use it in GitHub Desktop.
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>
`>
@antfu
Copy link

antfu commented Sep 3, 2020

学习了,tql

@Lucifier129
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment