├── .gitignore ├── .vscode └── settings.json ├── package.json ├── pnpm-lock.yaml ├── prettier.config.js └── src ├── parser.ts ├── resolve.ts ├── tokenizer.ts ├── tokens.ts ├── util.ts ├── v2 └── parser.ts └── v3 ├── example.ts ├── grammar.ts ├── parser.ts └── syntax.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "infer-gql", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "graphql": "^16.0.1", 13 | "typescript": "^4.5.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.3 2 | 3 | specifiers: 4 | graphql: ^16.0.1 5 | typescript: ^4.5.2 6 | 7 | dependencies: 8 | graphql: 16.0.1 9 | typescript: 4.5.2 10 | 11 | packages: 12 | 13 | /graphql/16.0.1: 14 | resolution: {integrity: sha512-oPvCuu6dlLdiz8gZupJ47o1clgb72r1u8NDBcQYjcV6G/iEdmE11B1bBlkhXRvV0LisP/SXRFP7tT6AgaTjpzg==} 15 | engines: {node: ^12.22.0 || ^14.16.0 || >=16.0.0} 16 | dev: false 17 | 18 | /typescript/4.5.2: 19 | resolution: {integrity: sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==} 20 | engines: {node: '>=4.2.0'} 21 | hasBin: true 22 | dev: false 23 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "$schema": "http://json.schemastore.org/prettierrc", 3 | "arrowParens": "always", 4 | "bracketSameLine": false, 5 | "bracketSpacing": true, 6 | "endOfLine": "lf", 7 | "printWidth": 100, 8 | "proseWrap": "always", 9 | "semi": true, 10 | "singleQuote": true, 11 | "tabWidth": 2, 12 | "trailingComma": "all", 13 | "useTabs": false, 14 | "overrides": [ 15 | { 16 | "files": [ 17 | "*.json", 18 | "*.yaml" 19 | ], 20 | "options": { 21 | "useTabs": false 22 | } 23 | } 24 | ] 25 | }; -------------------------------------------------------------------------------- /src/parser.ts: -------------------------------------------------------------------------------- 1 | import { Tokenize } from './tokenizer'; 2 | import { 3 | BangToken, 4 | ColonToken, 5 | CommaToken, 6 | CommentToken, 7 | EqualsToken, 8 | LeftBraceToken, 9 | LeftBracketToken, 10 | LeftParenToken, 11 | NameToken, 12 | PipeToken, 13 | RightBraceToken, 14 | RightBracketToken, 15 | RightParenToken, 16 | Token, 17 | } from './tokens'; 18 | import { ParseError } from './util'; 19 | 20 | export interface ObjectTypeNode< 21 | Name extends string = string, 22 | Fields extends FieldNode[] = FieldNode[], 23 | Interfaces extends string[] = string[], 24 | > { 25 | kind: 'Object'; 26 | name: Name; 27 | fields: Fields; 28 | interfaces: Interfaces; 29 | } 30 | 31 | export interface InterfaceTypeNode< 32 | Name extends string = string, 33 | Fields extends FieldNode[] = FieldNode[], 34 | Interfaces extends string[] = string[], 35 | > { 36 | kind: 'Interface'; 37 | name: Name; 38 | fields: Fields; 39 | interfaces: Interfaces; 40 | } 41 | 42 | export interface UnionTypeNode { 43 | kind: 'Union'; 44 | name: Name; 45 | members: Members; 46 | } 47 | 48 | export interface ScalarTypeNode { 49 | kind: 'Scalar'; 50 | name: Name; 51 | } 52 | 53 | export interface EnumTypeNode { 54 | kind: 'Enum'; 55 | name: Name; 56 | values: Values; 57 | } 58 | 59 | export interface InputTypeNode< 60 | Name extends string = string, 61 | Fields extends InputFieldNode[] = InputFieldNode[], 62 | > { 63 | kind: 'Input'; 64 | name: Name; 65 | fields: Fields; 66 | } 67 | 68 | export interface FieldNode< 69 | Name extends string = string, 70 | Args extends ArgNode[] = ArgNode[], 71 | Type extends TypeRefNode = TypeRefNode, 72 | > { 73 | kind: 'Field'; 74 | name: Name; 75 | args: Args; 76 | type: Type; 77 | } 78 | 79 | export interface InputFieldNode< 80 | Name extends string = string, 81 | Type extends TypeRefNode = TypeRefNode, 82 | > { 83 | kind: 'Field'; 84 | name: Name; 85 | type: Type; 86 | } 87 | 88 | export interface ArgNode { 89 | kind: 'Arg'; 90 | name: Name; 91 | type: Type; 92 | } 93 | 94 | export interface TypeRefNode< 95 | Name extends string = string, 96 | NonNull extends boolean = boolean, 97 | List extends boolean = boolean, 98 | ListItemNonNull extends boolean = boolean, 99 | > { 100 | kind: 'TypeRef'; 101 | name: Name; 102 | nonNull: NonNull; 103 | list: List; 104 | listItemNonNull: ListItemNonNull; 105 | } 106 | 107 | export type TypeNode = 108 | | ObjectTypeNode 109 | | InterfaceTypeNode 110 | | ScalarTypeNode 111 | | EnumTypeNode 112 | | UnionTypeNode 113 | | InputTypeNode; 114 | 115 | export type TypeResult = { Rest: unknown[]; Type: TypeNode }; 116 | export type ArgsResult = { Rest: unknown[]; Args: ArgNode[] }; 117 | export type FieldBlockResult = { Rest: unknown[]; Fields: FieldNode[] }; 118 | export type InputFieldBlockResult = { 119 | Rest: unknown[]; 120 | Fields: InputFieldNode[]; 121 | }; 122 | export type TypeRefResult = { Rest: unknown[]; Ref: TypeRefNode }; 123 | export type MembersResult = { Rest: unknown[]; Members: string[] }; 124 | export type EnumValuesResult = { 125 | Rest: unknown[]; 126 | Values: string[]; 127 | }; 128 | export type ImplementsResult = { Rest: unknown[]; Interfaces: string[] }; 129 | 130 | export type Parse = Tokenize extends infer Tokens 131 | ? Tokens extends Token[] 132 | ? ParseStatements 133 | : Tokens 134 | : never; 135 | 136 | export type ParseStatements = [] extends T 137 | ? Types 138 | : T extends [NameToken, ...infer Rest] 139 | ? ParseType 140 | : T extends [CommentToken, ...infer Rest] 141 | ? ParseStatements 142 | : UnexpectedToken; 143 | 144 | export type AddType< 145 | T extends TypeResult | ParseError, 146 | Types extends TypeNode[], 147 | > = T extends TypeResult ? ParseStatements : UnexpectedToken; 148 | 149 | export type ParseType = Type extends 'type' 150 | ? AddType, Types> 151 | : Type extends 'interface' 152 | ? AddType, Types> 153 | : Type extends 'union' 154 | ? AddType, Types> 155 | : Type extends 'scalar' 156 | ? AddType, Types> 157 | : Type extends 'enum' 158 | ? AddType, Types> 159 | : Type extends 'input' 160 | ? AddType, Types> 161 | : ParseError<`Unknown keyword ${Type}`>; 162 | 163 | export type UnexpectedToken = T extends Token 164 | ? ParseError<`Unexpected ${T['kind']} token (\`${T['value']}\`)`> 165 | : T extends unknown[] 166 | ? UnexpectedToken 167 | : T extends ParseError 168 | ? T 169 | : ParseError<'Unexpected token'>; 170 | 171 | export type ParseObjectType = T extends [NameToken, ...infer Rest] 172 | ? ParseImplements> extends { 173 | Rest: infer R2; 174 | Interfaces: infer Interfaces; 175 | } 176 | ? ParseFieldBlock> extends infer Block 177 | ? Block extends FieldBlockResult 178 | ? { 179 | Type: ObjectTypeNode< 180 | Name, 181 | Block['Fields'], 182 | Interfaces extends string[] ? Interfaces : [] 183 | >; 184 | Rest: Block['Rest']; 185 | } 186 | : Block 187 | : never 188 | : never 189 | : UnexpectedToken; 190 | 191 | export type ParseInterfaceType = T extends [NameToken, ...infer Rest] 192 | ? ParseImplements> extends { 193 | Rest: infer R2; 194 | Interfaces: infer Interfaces; 195 | } 196 | ? ParseFieldBlock> extends infer Block 197 | ? Block extends FieldBlockResult 198 | ? { 199 | Type: InterfaceTypeNode< 200 | Name, 201 | Block['Fields'], 202 | Interfaces extends string[] ? Interfaces : [] 203 | >; 204 | Rest: Block['Rest']; 205 | } 206 | : Block 207 | : never 208 | : never 209 | : UnexpectedToken; 210 | 211 | export type ParseInputType = T extends [NameToken, ...infer Rest] 212 | ? ParseInputFieldBlock> extends infer Block 213 | ? Block extends InputFieldBlockResult 214 | ? { 215 | Type: InputTypeNode; 216 | Rest: Block['Rest']; 217 | } 218 | : Block 219 | : never 220 | : UnexpectedToken; 221 | 222 | export type ParseScalarType = T extends [NameToken, ...infer Rest] 223 | ? { Type: ScalarTypeNode; Rest: Rest } 224 | : UnexpectedToken; 225 | 226 | export type ParseUnionType = T extends [ 227 | NameToken, 228 | EqualsToken, 229 | NameToken, 230 | ...infer Rest 231 | ] 232 | ? ParseUnionMembers extends infer R 233 | ? R extends MembersResult 234 | ? { Type: UnionTypeNode; Rest: R['Rest'] } 235 | : R 236 | : never 237 | : UnexpectedToken; 238 | 239 | export type ParseUnionMembers = T extends [PipeToken, ...infer Rest] 240 | ? Rest extends [NameToken, ...infer R2] 241 | ? ParseUnionMembers 242 | : UnexpectedToken 243 | : { Rest: T; Members: Acc }; 244 | 245 | export type ParseEnumType = T extends [NameToken, LeftBraceToken, ...infer Rest] 246 | ? ParseEnumValues extends infer R 247 | ? R extends EnumValuesResult 248 | ? { Type: EnumTypeNode; Rest: R['Rest'] } 249 | : R 250 | : never 251 | : UnexpectedToken; 252 | 253 | export type ParseEnumValues = T extends [RightBraceToken, ...infer Rest] 254 | ? { Rest: Rest; Values: Acc } 255 | : T extends [NameToken, ...infer Rest] 256 | ? ParseEnumValues 257 | : UnexpectedToken; 258 | 259 | export type ParseImplements = T extends [ 260 | NameToken<'implements'>, 261 | NameToken, 262 | ...infer Rest 263 | ] 264 | ? ParseImplementList 265 | : { Rest: T; Interfaces: [] }; 266 | 267 | export type ParseImplementList = T extends [CommaToken, ...infer Rest] 268 | ? Rest extends [NameToken, ...infer R2] 269 | ? ParseImplementList 270 | : UnexpectedToken 271 | : { Rest: T; Interfaces: Acc }; 272 | 273 | export type ParseFieldBlock = T extends [LeftBraceToken, ...infer Rest] 274 | ? ParseFields, []> 275 | : { Rest: T; Fields: [] }; 276 | 277 | export type ParseFields = T extends [ 278 | NameToken, 279 | LeftParenToken, 280 | ...infer Rest 281 | ] 282 | ? ParseArgs, []> extends infer R 283 | ? R extends ArgsResult 284 | ? ParseField 285 | : R 286 | : never 287 | : T extends [NameToken, ...infer Rest] 288 | ? ParseField 289 | : T extends [RightBraceToken, ...infer Rest] 290 | ? { Rest: Rest; Fields: Acc } 291 | : UnexpectedToken; 292 | 293 | export type ParseField< 294 | Name extends string, 295 | Args extends ArgNode[], 296 | T, 297 | Acc extends FieldNode[], 298 | > = T extends [ColonToken, ...infer Rest] 299 | ? ParseTypeRef extends infer R 300 | ? R extends TypeRefResult 301 | ? ParseFields]> 302 | : R 303 | : never 304 | : UnexpectedToken; 305 | 306 | export type ParseArgs = T extends [RightParenToken, ...infer Rest] 307 | ? { Args: Acc; Rest: Rest } 308 | : T extends [ 309 | ...([] extends Acc ? [] : [CommaToken]), 310 | NameToken, 311 | ColonToken, 312 | ...infer Rest 313 | ] 314 | ? ParseTypeRef extends infer R 315 | ? R extends TypeRefResult 316 | ? ParseArgs]> 317 | : R 318 | : never 319 | : UnexpectedToken; 320 | 321 | export type ParseInputFieldBlock = T extends [LeftBraceToken, ...infer Rest] 322 | ? ParseInputFields, []> 323 | : { Rest: T; Fields: [] }; 324 | 325 | export type ParseInputFields = T extends [ 326 | NameToken, 327 | ...infer Rest 328 | ] 329 | ? ParseInputField 330 | : T extends [RightBraceToken, ...infer Rest] 331 | ? { Rest: Rest; Fields: Acc } 332 | : UnexpectedToken; 333 | 334 | export type ParseInputField = T extends [ 335 | ColonToken, 336 | ...infer Rest 337 | ] 338 | ? ParseTypeRef extends infer R 339 | ? R extends TypeRefResult 340 | ? ParseInputFields]> 341 | : R 342 | : never 343 | : UnexpectedToken; 344 | 345 | export type ParseTypeRef = T extends [ 346 | LeftBracketToken, 347 | infer Name, 348 | BangToken, 349 | RightBracketToken, 350 | BangToken, 351 | ...infer Rest 352 | ] 353 | ? Name extends NameToken 354 | ? { Rest: Rest; Ref: TypeRefNode } 355 | : UnexpectedToken 356 | : T extends [LeftBracketToken, infer Name, RightBracketToken, BangToken, ...infer Rest] 357 | ? Name extends NameToken 358 | ? { Rest: Rest; Ref: TypeRefNode } 359 | : UnexpectedToken 360 | : T extends [LeftBracketToken, infer Name, BangToken, RightBracketToken, ...infer Rest] 361 | ? Name extends NameToken 362 | ? { Rest: Rest; Ref: TypeRefNode } 363 | : UnexpectedToken 364 | : T extends [LeftBracketToken, infer Name, RightBracketToken, ...infer Rest] 365 | ? Name extends NameToken 366 | ? { Rest: Rest; Ref: TypeRefNode } 367 | : UnexpectedToken 368 | : T extends [infer Name, BangToken, ...infer Rest] 369 | ? Name extends NameToken 370 | ? { Rest: Rest; Ref: TypeRefNode } 371 | : UnexpectedToken 372 | : T extends [infer Name, ...infer Rest] 373 | ? Name extends NameToken 374 | ? { Rest: Rest; Ref: TypeRefNode } 375 | : UnexpectedToken 376 | : UnexpectedToken; 377 | 378 | export type Consume = T extends [infer First, ...infer Rest] 379 | ? First extends U 380 | ? Consume 381 | : T 382 | : []; 383 | 384 | export type ConsumeUntil = T extends [infer First, ...infer Rest] 385 | ? First extends U 386 | ? Rest 387 | : ConsumeUntil 388 | : []; 389 | 390 | export type SDL = ` 391 | # comment 392 | scalar Date 393 | union Text = User | Post 394 | input UserInput { 395 | id: String! 396 | } 397 | type Query { user(input: UserInput, id: ID!): User} 398 | interface Authored implements Node { id: ID! author: User! } 399 | interface Node { id: ID! } 400 | type User { id: ID name: String! diet: Diet } 401 | type Post implements Authored, ID { author: User! } 402 | type Comment { author: User! } 403 | enum Diet { 404 | CARNIVOROUS 405 | HERBIVOROUS 406 | OMNIVORIOUS 407 | } 408 | `; 409 | 410 | export type R = Parse; 411 | -------------------------------------------------------------------------------- /src/resolve.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLResolveInfo } from 'graphql'; 2 | import { 3 | FieldNode, 4 | InterfaceTypeNode, 5 | ObjectTypeNode, 6 | Parse, 7 | TypeNode, 8 | TypeRefNode, 9 | } from './parser'; 10 | 11 | export interface BaseTypes { 12 | String: string; 13 | Int: number; 14 | Float: number; 15 | Boolean: boolean; 16 | ID: number | string; 17 | } 18 | 19 | export type MaybePromise = T | Promise; 20 | 21 | export type ShapeFromName = Name extends keyof Types 22 | ? Types[Name] 23 | : {}; 24 | 25 | export type ShapeFromTypeRef = ShapeFromName< 26 | Types, 27 | Ref['name'] 28 | > extends infer Shape 29 | ? Ref extends { list: true } 30 | ? Ref extends { nonNull: true } 31 | ? 32 | | (Ref extends { listItemNonNull: true } ? Shape : Shape | null | undefined)[] 33 | | null 34 | | undefined 35 | : (Ref extends { listItemNonNull: true } ? Shape : Shape | null | undefined)[] 36 | : Ref extends { nonNull: true } 37 | ? Shape 38 | : Shape | null | undefined 39 | : never; 40 | 41 | export type ResolverFromField< 42 | Parent extends string, 43 | T extends FieldNode, 44 | Types extends BaseTypes = BaseTypes, 45 | Context extends {} = {}, 46 | > = ( 47 | parent: Parent extends keyof Types ? Types[Parent] : {}, 48 | args: { 49 | [K in T['args'][number] as K['name']]: ShapeFromTypeRef; 50 | }, 51 | ctx: {}, 52 | info: GraphQLResolveInfo, 53 | ) => MaybePromise>; 54 | 55 | export type ResolveTypes< 56 | T extends string, 57 | Types extends BaseTypes = BaseTypes, 58 | Context extends {} = {}, 59 | > = Parse extends infer Tokens 60 | ? Tokens extends TypeNode[] 61 | ? { 62 | [K in Tokens[number] as K extends ObjectTypeNode ? K['name'] : never]: { 63 | [F in (K & ObjectTypeNode)['fields'][number] as F['name']]: ResolverFromField< 64 | K['name'], 65 | F, 66 | Types, 67 | Context 68 | >; 69 | }; 70 | } & { 71 | [K in Tokens[number] as K extends InterfaceTypeNode ? K['name'] : never]?: { 72 | [F in (K & InterfaceTypeNode)['fields'][number] as F['name']]?: ResolverFromField< 73 | K['name'], 74 | F, 75 | Types, 76 | Context 77 | >; 78 | }; 79 | } 80 | : Tokens 81 | : never; 82 | 83 | export type SDL = ` 84 | # comment 85 | scalar Date 86 | union Text = User | Post 87 | input UserInput { 88 | id: String! 89 | } 90 | type Query { 91 | user(input: UserInput, id: ID!): User! 92 | users(input: UserInput, id: ID!): [User!]! 93 | } 94 | interface Authored implements Node { id: ID! author: User! } 95 | interface Node { id: ID! } 96 | type User { id: ID name: String! diet: Diet } 97 | type Post implements Authored, ID { author: User! } 98 | type Comment { author: User! } 99 | enum Diet { 100 | CARNIVOROUS 101 | HERBIVOROUS 102 | OMNIVORIOUS 103 | } 104 | `; 105 | 106 | export const resolvers: ResolveTypes = { 107 | Query: { 108 | user: (parent, args, ctx, info) => Promise.resolve({ id: 123, name: 'Name' }), 109 | users: (parent, args, ctx, info) => Promise.resolve([{ id: 123, name: 'Name' }]), 110 | }, 111 | User: { 112 | id: (parent, args, ctx, info) => parent.id, 113 | name: (parent, args, ctx, info) => parent.name, 114 | diet: () => 'CARNIVOROUS', 115 | }, 116 | Post: { 117 | author: (parent, args, ctx, info) => Promise.resolve({ id: 123, name: 'Name' }), 118 | }, 119 | Comment: { 120 | author: (parent, args, ctx, info) => Promise.resolve({ id: 123, name: 'Name' }), 121 | }, 122 | }; 123 | -------------------------------------------------------------------------------- /src/tokenizer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Whitespace, 3 | TrimLeft, 4 | Digit, 5 | NameChar, 6 | NameStart, 7 | ParseError, 8 | } from "./util"; 9 | import { 10 | AmpToken, 11 | AtToken, 12 | BangToken, 13 | ColonToken, 14 | CommaToken, 15 | CommentToken, 16 | DollarToken, 17 | EqualsToken, 18 | FloatToken, 19 | LeftBraceToken, 20 | LeftBracketToken, 21 | LeftParenToken, 22 | NameToken, 23 | PipeToken, 24 | RightBraceToken, 25 | RightBracketToken, 26 | RightParenToken, 27 | SpreadToken, 28 | StringToken, 29 | Token, 30 | } from "./tokens"; 31 | 32 | export type ParseName< 33 | T extends string, 34 | Prefix extends string, 35 | Acc extends Token[] 36 | > = T extends `${NameChar}${infer Rest}` 37 | ? ParseName< 38 | Rest, 39 | T extends `${infer Name}${Rest}` ? `${Prefix}${Name}` : never, 40 | Acc 41 | > 42 | : Tokenize]>; 43 | 44 | export type ParseString< 45 | T extends string, 46 | Str extends string, 47 | Acc extends Token[] 48 | > = T extends `${infer S}"${infer Rest}` 49 | ? S extends `${string}\\` 50 | ? S extends `${string}\\\\` 51 | ? Tokenize]> 52 | : ParseString 53 | : Tokenize]> 54 | : ParseError<"Unterminated string literal">; 55 | 56 | export type ParseBlockString< 57 | T extends string, 58 | Str extends string, 59 | Acc extends Token[] 60 | > = T extends `${infer S}"""${infer Rest}` 61 | ? S extends `${string}\\` 62 | ? S extends `${string}\\\\` 63 | ? Tokenize]> 64 | : ParseBlockString 65 | : Tokenize]> 66 | : ParseError<"Unterminated block string literal">; 67 | 68 | export interface SymbolTokens { 69 | $: DollarToken; 70 | "!": BangToken; 71 | "&": AmpToken; 72 | "(": LeftParenToken; 73 | ")": RightParenToken; 74 | "...": SpreadToken; 75 | ":": ColonToken; 76 | "=": EqualsToken; 77 | "@": AtToken; 78 | "[": LeftBracketToken; 79 | "]": RightBracketToken; 80 | "{": LeftBraceToken; 81 | "|": PipeToken; 82 | "}": RightBraceToken; 83 | ",": CommaToken; 84 | } 85 | 86 | export type Tokenize = T extends "" 87 | ? Acc 88 | : string extends T 89 | ? ParseError<"SDL string must have a literal type"> 90 | : T extends `${Whitespace}${infer Rest}` 91 | ? Tokenize, Acc> 92 | : T extends `${keyof SymbolTokens}${infer Rest}` 93 | ? T extends `${infer S}${Rest}` 94 | ? Tokenize 95 | : never 96 | : T extends `#${infer Comment}\n${infer Rest}` 97 | ? Tokenize]> 98 | : T extends `#${infer Comment}` 99 | ? [...Acc, CommentToken] 100 | : T extends `"${infer S}` 101 | ? ParseString 102 | : T extends `"""${infer S}` 103 | ? ParseBlockString 104 | : T extends `${Digit | "."}${infer Rest}` // TODO make this more correct 105 | ? Tokenize 106 | : T extends `${NameStart}${infer Rest}` 107 | ? ParseName 108 | : ParseError<`Unknown token at "${T}"`>; 109 | -------------------------------------------------------------------------------- /src/tokens.ts: -------------------------------------------------------------------------------- 1 | import { TokenKind } from "graphql"; 2 | 3 | export interface NameToken { 4 | kind: TokenKind.NAME; 5 | value: T; 6 | } 7 | 8 | export interface BangToken { 9 | kind: TokenKind.BANG; 10 | value: "!"; 11 | } 12 | 13 | export interface DollarToken { 14 | kind: TokenKind.DOLLAR; 15 | value: "$"; 16 | } 17 | export interface AmpToken { 18 | kind: TokenKind.AMP; 19 | value: "&"; 20 | } 21 | export interface LeftParenToken { 22 | kind: TokenKind.PAREN_L; 23 | value: "("; 24 | } 25 | export interface RightParenToken { 26 | kind: TokenKind.PAREN_R; 27 | value: ")"; 28 | } 29 | export interface SpreadToken { 30 | kind: TokenKind.SPREAD; 31 | value: "..."; 32 | } 33 | export interface ColonToken { 34 | kind: TokenKind.COLON; 35 | value: ":"; 36 | } 37 | export interface EqualsToken { 38 | kind: TokenKind.EQUALS; 39 | value: "="; 40 | } 41 | export interface AtToken { 42 | kind: TokenKind.AT; 43 | value: "@"; 44 | } 45 | export interface LeftBracketToken { 46 | kind: TokenKind.BRACKET_L; 47 | value: "["; 48 | } 49 | export interface RightBracketToken { 50 | kind: TokenKind.BRACKET_R; 51 | value: "]"; 52 | } 53 | export interface LeftBraceToken { 54 | kind: TokenKind.BRACE_L; 55 | value: "{"; 56 | } 57 | export interface PipeToken { 58 | kind: TokenKind.PIPE; 59 | value: "|"; 60 | } 61 | export interface RightBraceToken { 62 | kind: TokenKind.BRACE_R; 63 | value: "}"; 64 | } 65 | export interface IntToken { 66 | kind: TokenKind.INT; 67 | value: T; 68 | } 69 | export interface FloatToken { 70 | kind: TokenKind.FLOAT; 71 | value: T; 72 | } 73 | export interface StringToken { 74 | kind: TokenKind.STRING; 75 | value: T; 76 | } 77 | export interface BlockStringToken { 78 | kind: TokenKind.BLOCK_STRING; 79 | value: T; 80 | } 81 | export interface CommentToken { 82 | kind: TokenKind.COMMENT; 83 | value: T; 84 | } 85 | 86 | export interface CommaToken { 87 | kind: "Comma"; 88 | value: ","; 89 | } 90 | 91 | export type Token = 92 | | NameToken 93 | | BangToken 94 | | DollarToken 95 | | AmpToken 96 | | LeftParenToken 97 | | RightParenToken 98 | | SpreadToken 99 | | ColonToken 100 | | EqualsToken 101 | | AtToken 102 | | LeftBracketToken 103 | | RightBracketToken 104 | | LeftBraceToken 105 | | PipeToken 106 | | RightBraceToken 107 | | IntToken 108 | | FloatToken 109 | | StringToken 110 | | CommentToken 111 | | CommaToken; 112 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | export type Whitespace = ' ' | '\n' | '\r' | '\t'; 2 | 3 | export type Alphabet = 4 | | 'a' 5 | | 'b' 6 | | 'c' 7 | | 'd' 8 | | 'e' 9 | | 'f' 10 | | 'g' 11 | | 'h' 12 | | 'i' 13 | | 'j' 14 | | 'k' 15 | | 'l' 16 | | 'm' 17 | | 'n' 18 | | 'o' 19 | | 'p' 20 | | 'q' 21 | | 'r' 22 | | 's' 23 | | 't' 24 | | 'u' 25 | | 'v' 26 | | 'w' 27 | | 'x' 28 | | 'y' 29 | | 'z'; 30 | 31 | export type Letter = Alphabet | Uppercase; 32 | export type Digit = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0'; 33 | export type NameStart = Letter | '_'; 34 | export type NameChar = NameStart | Digit; 35 | 36 | export type TrimLeft = T extends `${Whitespace}${infer Rest}` 37 | ? TrimLeft 38 | : T; 39 | 40 | export type TrimRight = T extends `${infer Rest}${Whitespace}` 41 | ? TrimRight 42 | : T; 43 | 44 | export type Trim = TrimRight>; 45 | 46 | export interface ParseError { 47 | kind: 'ParseError'; 48 | message: T; 49 | } 50 | 51 | export type Merge = { [K in keyof T]: T[K] }; 52 | -------------------------------------------------------------------------------- /src/v2/parser.ts: -------------------------------------------------------------------------------- 1 | import { Trim, TrimLeft } from '../util'; 2 | 3 | export type SDL = ` 4 | # comment 5 | scalar Date 6 | union Text = User | Post 7 | input UserInput { 8 | id: String! 9 | } 10 | type Query { 11 | user(input: UserInput, id: ID!): User! 12 | users(input: UserInput, id: ID!): [User!]! 13 | } 14 | interface Authored implements Node { id: ID! author: User! } 15 | interface Node { id: ID! } 16 | type User { id: ID name: String! diet: Diet } 17 | type Post implements Authored, ID { author: User! } 18 | type Comment { author: User! } 19 | enum Diet { 20 | CARNIVOROUS 21 | HERBIVOROUS 22 | OMNIVORIOUS 23 | } 24 | `; 25 | 26 | interface PairSection { 27 | kind: 'PairSection'; 28 | start: string; 29 | end: string; 30 | } 31 | 32 | interface Section { 33 | kind: 'Section'; 34 | content: string; 35 | } 36 | 37 | interface Keyword extends Section { 38 | type: 'Keyword'; 39 | content: 40 | | 'type' 41 | | 'interface' 42 | | 'enum' 43 | | 'union' 44 | | 'input' 45 | | 'scalar' 46 | | 'directive' 47 | | 'query' 48 | | 'mutation' 49 | | 'subscription' 50 | | 'fragment' 51 | | 'extends'; 52 | } 53 | 54 | interface TextSection extends Section { 55 | type: 'TextSection'; 56 | } 57 | 58 | type Syntax = PairSection | Section; 59 | 60 | interface Comment extends PairSection { 61 | type: 'Comment'; 62 | start: '#'; 63 | end: '\n'; 64 | } 65 | 66 | interface Block extends PairSection { 67 | type: 'Block'; 68 | start: '{'; 69 | end: '}'; 70 | } 71 | 72 | type Grammar = Comment | Block | Keyword | TextSection; 73 | 74 | type MatchString = { 75 | [K in Key]: T extends `${infer Before}${K}${infer After}` 76 | ? { key: K; match: `${Before}${string}`; before: Before; after: After } 77 | : never; 78 | }; 79 | 80 | type MatchRecord = { match: string; before: string; after: string; key: string }; 81 | 82 | type GetMatch = K extends keyof Matches 83 | ? (Matches[keyof Matches] & MatchRecord)['match'] extends (Matches[K] & MatchRecord)['match'] 84 | ? Matches[K] 85 | : never 86 | : never; 87 | 88 | type First = [MatchString] extends [infer Matches] 89 | ? { 90 | [K in Key]: GetMatch; 91 | }[Key] 92 | : never; 93 | 94 | type MatchPairStart = Grammar extends infer S 95 | ? S extends PairSection 96 | ? T extends `${S['start']}${infer Content}${S['end']}${infer After}` 97 | ? { 98 | type: S; 99 | content: Trim; 100 | after: After; 101 | } 102 | : never 103 | : never 104 | : never; 105 | 106 | type MatchTextSection = First< 107 | T, 108 | (Grammar & PairSection)['start'] 109 | > extends { before: infer Content; key: infer K; after: infer After } 110 | ? { 111 | type: Text; 112 | content: Trim; 113 | after: `${K & string}${After & string}`; 114 | } 115 | : { 116 | type: Text; 117 | content: T; 118 | after: ''; 119 | }; 120 | 121 | type ParseGrammar< 122 | Grammar extends Syntax, 123 | T extends string, 124 | Nodes extends unknown[] = [], 125 | > = T extends Trim 126 | ? T extends '' 127 | ? Nodes 128 | : MatchPairStart extends infer Pair 129 | ? [Pair] extends [never] 130 | ? MatchTextSection extends infer Text 131 | ? [Text] extends [never] 132 | ? never 133 | : ParseGrammar< 134 | Grammar, 135 | (Text & { after: string })['after'], 136 | [ 137 | ...Nodes, 138 | { 139 | type: Section; 140 | content: (Text & Section)['content']; 141 | }, 142 | ] 143 | > 144 | : never 145 | : [Pair] extends [ 146 | { 147 | type: infer Type; 148 | content: infer Content; 149 | after: infer After; 150 | }, 151 | ] 152 | ? ParseGrammar< 153 | Grammar, 154 | After & string, 155 | [ 156 | ...Nodes, 157 | { 158 | type: Type; 159 | content: Content; 160 | }, 161 | ] 162 | > 163 | : never 164 | : never 165 | : ParseGrammar, Nodes>; 166 | 167 | type Result = ParseGrammar; 168 | -------------------------------------------------------------------------------- /src/v3/example.ts: -------------------------------------------------------------------------------- 1 | import { ParseNode } from './parser'; 2 | import { DocumentBlock, QueryBlock } from './grammar'; 3 | 4 | export type SDL = /* graphql */ ` 5 | directive @deprecated( 6 | reason: String = "No longer supported" 7 | ) on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION | ENUM_VALUE 8 | 9 | """ 10 | block 11 | comment 12 | """ 13 | # comment 14 | scalar Date2 15 | union Text = User | Post 16 | input UserInput { 17 | id: String! = "1" @test @deprecated(reason: "test") 18 | } 19 | 20 | type Query { 21 | me: User! @test @deprecated(reason: "test") 22 | user(input: UserInput = { }, id: ID!): User! 23 | # comment 24 | users(input: UserInput, id: ID!): [User!]! 25 | } 26 | 27 | interface Node { id: ID! } 28 | interface Authored implements Node { id: ID! author: User! } 29 | type User @test @deprecated(reason: "test") { id: ID name: String! diet: Diet friends: [User!]! } 30 | type Post implements Authored , ID { author: User! } 31 | type Comment { author: User! } 32 | enum Diet { 33 | CARNIVOROUS 34 | HERBIVOROUS 35 | OMNIVORIOUS 36 | } 37 | `; 38 | 39 | export type ExampleQuery = /* graphql */ ` 40 | # comment 41 | query exampleQuery($id: ID) { 42 | user(id: "123") { 43 | ID 44 | } 45 | me { 46 | id 47 | name 48 | # comment 49 | """ 50 | block comment 51 | """ 52 | ...UserSelection 53 | ...on User { 54 | diet 55 | } 56 | } 57 | } 58 | 59 | fragment UserSelection on User { 60 | name 61 | friends { 62 | id 63 | } 64 | } 65 | `; 66 | 67 | export type ParseDocument = ParseNode; 68 | export type ParseQuery = ParseNode; 69 | 70 | export type Parsed = ParseDocument; 71 | 72 | export type ParsedQuery = ParseQuery['node'][1]['body']; 73 | -------------------------------------------------------------------------------- /src/v3/grammar.ts: -------------------------------------------------------------------------------- 1 | import { Block, Condition, Group, Line, List, Literal, SyntaxNode, Word } from './syntax'; 2 | 3 | export interface DocumentBlock 4 | extends Block<{ 5 | '"""': BlockCommentSyntax; 6 | '#': CommentSyntax; 7 | scalar: ScalarSyntax; 8 | input: InputSyntax; 9 | enum: EnumSyntax; 10 | type: TypeSyntax; 11 | interface: InterfaceSyntax; 12 | union: UnionSyntax; 13 | directive: DirectiveDefinitionSyntax; 14 | }> { 15 | end: ''; 16 | } 17 | 18 | export interface QueryBlock 19 | extends Block<{ 20 | '"""': BlockCommentSyntax; 21 | '#': CommentSyntax; 22 | query: QuerySyntax; 23 | fragment: FragmentSyntax; 24 | }> { 25 | end: ''; 26 | } 27 | 28 | export interface ScalarSyntax 29 | extends SyntaxNode< 30 | 'Scalar', 31 | [{ name: 'kind'; kind: Literal<'scalar'> }, { name: 'name'; kind: Word<' ' | '\n'> }] 32 | > {} 33 | 34 | export interface DirectiveDefinitionSyntax 35 | extends SyntaxNode< 36 | 'DirectiveDefinition', 37 | [ 38 | { name: 'kind'; kind: Literal<'directive'> }, 39 | { name: 'name'; kind: Word<' ' | '\n' | '('> }, 40 | { name: 'args'; kind: FieldArguments; optional: true }, 41 | { name: 'onKeyword'; kind: Literal<'on'> }, 42 | { name: 'on'; kind: List, '|'> }, 43 | ] 44 | > {} 45 | 46 | export interface Directive 47 | extends SyntaxNode< 48 | 'Directive', 49 | [ 50 | { name: '@'; kind: Literal<'@'> }, 51 | { name: 'name'; kind: Word<' ' | '\n' | '('> }, 52 | { name: 'args'; kind: Arguments; optional: true }, 53 | ] 54 | > {} 55 | 56 | export interface Arguments extends Block<{}, Argument> { 57 | end: `)`; 58 | start: '('; 59 | separator: ','; 60 | } 61 | 62 | export interface Argument 63 | extends SyntaxNode< 64 | 'Argument', 65 | [{ name: 'name'; kind: Word<' ' | '\n' | ':' | ')'> }, { name: 'value'; kind: ArgumentValue }] 66 | > {} 67 | 68 | export interface ArgumentValue 69 | extends SyntaxNode< 70 | 'ArgumentValue', 71 | [{ name: ':'; kind: ColonSyntax }, { name: 'value'; kind: Word<',' | ')'> }] 72 | > {} 73 | 74 | export interface UnionSyntax 75 | extends SyntaxNode< 76 | 'Union', 77 | [ 78 | { name: 'kind'; kind: Literal<'union'> }, 79 | { name: 'name'; kind: Word<' ' | '\n'> }, 80 | { name: '='; kind: EqualsSyntax }, 81 | { name: 'members'; kind: List, '|'> }, 82 | ] 83 | > {} 84 | 85 | export interface CommentSyntax 86 | extends SyntaxNode< 87 | 'Comment', 88 | [{ name: 'kind'; kind: Literal<'#'> }, { name: 'body'; kind: Line }] 89 | > {} 90 | 91 | export interface EqualsSyntax extends Literal<'='> {} 92 | 93 | export interface ColonSyntax extends Literal<':'> {} 94 | 95 | export interface ExclamationSyntax extends Literal<'!'> {} 96 | 97 | export interface BlockCommentSyntax 98 | extends SyntaxNode<'BlockComment', [{ name: 'body'; kind: Group<'"""', '"""'> }]> {} 99 | 100 | export interface TypeBody 101 | extends Block< 102 | { 103 | '"""': BlockCommentSyntax; 104 | '#': CommentSyntax; 105 | }, 106 | FieldDefinition 107 | > { 108 | end: `}`; 109 | start: '{'; 110 | } 111 | 112 | export interface InputBody 113 | extends Block< 114 | { 115 | '"""': BlockCommentSyntax; 116 | '#': CommentSyntax; 117 | }, 118 | InputFieldDefinition 119 | > { 120 | end: `}`; 121 | start: '{'; 122 | } 123 | 124 | export interface EnumBody 125 | extends Block< 126 | { 127 | '"""': BlockCommentSyntax; 128 | '#': CommentSyntax; 129 | }, 130 | Word 131 | > { 132 | end: `}`; 133 | start: '{'; 134 | } 135 | 136 | export interface FieldArguments extends Block<{}, InputFieldDefinition> { 137 | end: `)`; 138 | start: '('; 139 | separator: ','; 140 | } 141 | 142 | export interface DefaultValue 143 | extends SyntaxNode< 144 | 'DefaultValue', 145 | [{ name: '='; kind: EqualsSyntax }, { name: 'value'; kind: Word<',' | ')' | '@'> }] 146 | > {} 147 | 148 | export interface InputFieldDefinition 149 | extends SyntaxNode< 150 | 'InputFieldDefinition', 151 | [ 152 | { name: 'name'; kind: Word<' ' | '\n' | ':' | ')'> }, 153 | { name: ':'; kind: ColonSyntax }, 154 | { name: 'type'; kind: Word<' ' | '\n' | ',' | ')'> }, 155 | { name: 'default'; kind: DefaultValue; optional: true }, 156 | { name: 'directives'; kind: Directive; repeat: true }, 157 | ] 158 | > {} 159 | 160 | export interface FieldDefinition 161 | extends SyntaxNode< 162 | 'FieldDefinition', 163 | [ 164 | { name: 'name'; kind: Word<' ' | '\n' | ':' | '('> }, 165 | { name: 'args'; kind: FieldArguments; optional: true }, 166 | { name: ':'; kind: ColonSyntax }, 167 | { name: 'type'; kind: Word<' ' | '\n' | ',' | ')' | '@'> }, 168 | { name: 'directives'; kind: Directive; repeat: true }, 169 | ] 170 | > {} 171 | 172 | export interface TypeSyntax 173 | extends SyntaxNode< 174 | 'Type', 175 | [ 176 | { name: 'kind'; kind: Literal<'type'> }, 177 | { name: 'name'; kind: Word<' ' | '\n'> }, 178 | { name: 'implements'; kind: ImplementsList; optional: true }, 179 | { name: 'directives'; kind: Directive; repeat: true }, 180 | { name: 'body'; kind: TypeBody }, 181 | ] 182 | > {} 183 | 184 | export interface ImplementsList 185 | extends SyntaxNode< 186 | 'Implements', 187 | [ 188 | { name: 'kind'; kind: Literal<'implements'> }, 189 | { name: 'interfaces'; kind: List, ','>; optional: true }, 190 | ] 191 | > {} 192 | 193 | export interface InterfaceSyntax 194 | extends SyntaxNode< 195 | 'Interface', 196 | [ 197 | { name: 'kind'; kind: Literal<'interface'> }, 198 | { name: 'name'; kind: Word<' ' | '\n'> }, 199 | { name: 'implements'; kind: ImplementsList; optional: true }, 200 | { name: 'body'; kind: TypeBody }, 201 | ] 202 | > {} 203 | 204 | export interface InputSyntax 205 | extends SyntaxNode< 206 | 'Input', 207 | [ 208 | { name: 'kind'; kind: Literal<'input'> }, 209 | { name: 'name'; kind: Word<' ' | '\n'> }, 210 | { name: 'body'; kind: InputBody }, 211 | ] 212 | > {} 213 | 214 | export interface EnumSyntax 215 | extends SyntaxNode< 216 | 'Enum', 217 | [ 218 | { name: 'kind'; kind: Literal<'enum'> }, 219 | { name: 'name'; kind: Word<' ' | '\n'> }, 220 | { name: 'body'; kind: EnumBody }, 221 | ] 222 | > {} 223 | 224 | export interface QuerySyntax 225 | extends SyntaxNode< 226 | 'Query', 227 | [ 228 | { name: 'kind'; kind: Literal<'query'> }, 229 | { name: 'name'; kind: Word<' ' | '\n' | '(' | '{'>; optional: true }, 230 | { name: 'variables'; kind: FieldArguments; optional: true }, 231 | { name: 'body'; kind: QueryBody }, 232 | ] 233 | > {} 234 | 235 | export interface QueryBody 236 | extends Block< 237 | { 238 | '"""': BlockCommentSyntax; 239 | '#': CommentSyntax; 240 | '...': FragmentSpread; 241 | }, 242 | FieldSelection 243 | > { 244 | end: `}`; 245 | start: '{'; 246 | } 247 | 248 | export interface FieldSelection 249 | extends SyntaxNode< 250 | 'FieldSelection', 251 | [ 252 | { name: 'name'; kind: Word<' ' | '\n' | ':' | '(' | ' {' | '}'> }, 253 | { name: 'args'; kind: Arguments; optional: true }, 254 | { name: 'selections'; kind: QueryBody; optional: true }, 255 | ] 256 | > {} 257 | 258 | export interface FragmentSyntax 259 | extends SyntaxNode< 260 | 'Fragment', 261 | [ 262 | { name: 'kind'; kind: Literal<'fragment'> }, 263 | { name: 'name'; kind: Word<' ' | '\n'> }, 264 | { name: 'on'; kind: Literal<'on'> }, 265 | { name: 'type'; kind: Word<' ' | '\n' | '{'> }, 266 | { name: 'body'; kind: QueryBody }, 267 | ] 268 | > {} 269 | 270 | export interface FragmentSpread 271 | extends SyntaxNode< 272 | 'FragmentSpread', 273 | [ 274 | { name: '...'; kind: Literal<'...'> }, 275 | { name: 'fragment'; kind: Condition<`on ${string}`, InlineFragment, Word<' ' | '\n' | '}'>> }, 276 | ] 277 | > {} 278 | 279 | export interface InlineFragment 280 | extends SyntaxNode< 281 | 'InlineFragment', 282 | [ 283 | { name: 'on'; kind: Literal<'on'> }, 284 | { name: 'type'; kind: Word<' ' | '\n' | '{'> }, 285 | { name: 'body'; kind: QueryBody }, 286 | ] 287 | > {} 288 | -------------------------------------------------------------------------------- /src/v3/parser.ts: -------------------------------------------------------------------------------- 1 | import { TrimLeft } from '../util'; 2 | import { 3 | Block, 4 | Group, 5 | Line, 6 | List, 7 | Literal, 8 | SyntaxNode, 9 | Syntax, 10 | SyntaxChild, 11 | Word, 12 | Condition, 13 | } from './syntax'; 14 | 15 | export type Normalize = T extends object ? { [K in keyof T]: T[K] } : T; 16 | 17 | type NodeWithChildren = ParseChildren< 18 | T, 19 | S['children'] 20 | > extends infer Children 21 | ? Children extends { map: infer Map; remaining: infer R } 22 | ? { 23 | node: Normalize< 24 | Map & { 25 | syntaxType: S; 26 | } 27 | >; 28 | remaining: R; 29 | } 30 | : never 31 | : never; 32 | 33 | type ParseLiteral< 34 | T extends string, 35 | S extends Literal, 36 | > = TrimLeft extends `${S['value']}${infer Rest}` 37 | ? { node: S['value']; remaining: Rest } 38 | : never; 39 | 40 | type BlockEnd = S['end'] extends '' 41 | ? '' extends T 42 | ? '' 43 | : false 44 | : T extends `${S['end']}${infer R}` 45 | ? R 46 | : false; 47 | 48 | type AddBlockChild = Child extends { 49 | node: unknown; 50 | remaining: string; 51 | } 52 | ? string extends S['separator'] 53 | ? ParseBlockContent, S, [...Acc, Child['node']]> 54 | : TrimLeft extends `${S['separator']}${infer R}` 55 | ? ParseBlockContent, S, [...Acc, Child['node']]> 56 | : ParseBlockContent, S, [...Acc, Child['node']]> 57 | : never; 58 | 59 | export type ParseBlockContent = BlockEnd< 60 | T, 61 | S 62 | > extends infer End 63 | ? End extends string 64 | ? { 65 | node: Acc; 66 | remaining: End; 67 | } 68 | : { 69 | [K in keyof S['types']]: T extends `${K & string}${string}` ? S['types'][K] : never; 70 | }[keyof S['types']] extends infer Type 71 | ? [Type] extends [never] 72 | ? Syntax extends S['default'] 73 | ? never 74 | : AddBlockChild, Acc> 75 | : AddBlockChild, Acc> 76 | : never 77 | : never; 78 | 79 | export type ParseBlockSyntax = string extends S['start'] 80 | ? ParseBlockContent 81 | : T extends `${S['start']}${infer Rest}` 82 | ? ParseBlockContent, S, []> 83 | : never; 84 | 85 | export type ParseConditional = ParseNode< 86 | T, 87 | T extends S['test'] ? S['pass'] : S['fail'] 88 | >; 89 | 90 | type ParseLineSyntax = T extends `${infer L}\n${infer Rest}` 91 | ? { 92 | node: L; 93 | remaining: Rest; 94 | } 95 | : { 96 | node: T; 97 | remaining: ''; 98 | }; 99 | 100 | export type ParseWordSyntax< 101 | T extends string, 102 | S extends Word, 103 | > = TrimLeft extends `${infer Word}${S['end']}${string}` 104 | ? { [K in Word]: [Word] extends [`${K}${string}`] ? K : never }[Word] extends infer W 105 | ? { 106 | node: W; 107 | remaining: T extends `${W & string}${infer R}` ? R : never; 108 | } 109 | : never 110 | : { node: TrimLeft; remaining: '' }; 111 | 112 | type ParseGroupSyntax< 113 | T extends string, 114 | S extends Group, 115 | > = TrimLeft extends `${S['start']}${infer Body}` 116 | ? Body extends `${infer Content}${S['end']}${infer R}` 117 | ? { 118 | node: Content; 119 | remaining: R; 120 | } 121 | : never 122 | : never; 123 | 124 | type ParseListSyntax = ParseNode< 125 | T, 126 | S['ofType'] 127 | > extends infer Parsed 128 | ? [Parsed] extends [never] 129 | ? { remaining: T; node: { type: T; items: Nodes } } 130 | : Parsed extends { remaining: infer R; node: infer N } 131 | ? TrimLeft extends `${S['separator']}${infer Rest}` 132 | ? ParseListSyntax 133 | : { remaining: R; node: { type: S; items: [...Nodes, N] } } 134 | : never 135 | : never; 136 | 137 | type ParseChildren< 138 | T extends string, 139 | Children, 140 | End = null, 141 | Map extends {} = {}, 142 | > = [] extends Children 143 | ? { remaining: T; map: Map } 144 | : TrimLeft extends `${End & string}${string}` 145 | ? { remaining: T; map: Map } 146 | : Children extends [infer Child, ...infer Rest] 147 | ? Child extends SyntaxChild 148 | ? Child['repeat'] extends true 149 | ? ParseNodes extends infer ParsedChild 150 | ? ParsedChild extends { nodes: infer Nodes; remaining: infer Remaining } 151 | ? ParseChildren 152 | : ParsedChild 153 | : never 154 | : Child['optional'] extends true 155 | ? ParseNode extends infer ParsedChild 156 | ? [ParsedChild] extends [never] 157 | ? ParseChildren 158 | : ParsedChild extends { node: infer Node; remaining: infer Remaining } 159 | ? ParseChildren 160 | : { remaining: T; map: Map } 161 | : never 162 | : ParseNode extends infer ParsedChild 163 | ? ParsedChild extends { node: infer Node; remaining: infer Remaining } 164 | ? ParseChildren 165 | : { remaining: T; map: Map } 166 | : never 167 | : never 168 | : never; 169 | 170 | export type ParseNode = S extends Literal 171 | ? ParseLiteral 172 | : S extends SyntaxNode 173 | ? NodeWithChildren 174 | : S extends Line 175 | ? ParseLineSyntax 176 | : S extends Word 177 | ? ParseWordSyntax, S> 178 | : S extends Group 179 | ? ParseGroupSyntax 180 | : S extends List 181 | ? ParseListSyntax 182 | : S extends Block 183 | ? ParseBlockSyntax, S> 184 | : S extends Condition 185 | ? ParseConditional 186 | : { 187 | node: { 188 | type: null; 189 | expected: S; 190 | }; 191 | remaining: T; 192 | kind: S; 193 | }; 194 | 195 | type ParseNodes = ParseNode< 196 | T, 197 | S 198 | > extends infer ParsedNode 199 | ? [ParsedNode] extends [never] 200 | ? { remaining: T; nodes: Nodes } 201 | : ParsedNode extends { remaining: infer R; node: infer N } 202 | ? ParseNodes 203 | : never 204 | : never; 205 | -------------------------------------------------------------------------------- /src/v3/syntax.ts: -------------------------------------------------------------------------------- 1 | export interface SyntaxChild { 2 | name: string; 3 | kind: Syntax; 4 | repeat?: boolean; 5 | optional?: boolean; 6 | } 7 | 8 | export interface Syntax { 9 | kind: 'literal' | 'word' | 'line' | 'group' | 'list' | 'block' | 'node' | 'conditional'; 10 | trim: boolean; 11 | children: Children; 12 | } 13 | 14 | export interface Literal extends Syntax<[]> { 15 | kind: 'literal'; 16 | value: Value; 17 | } 18 | 19 | export interface Word extends Syntax<[]> { 20 | kind: 'word'; 21 | value: string; 22 | end: End; 23 | } 24 | 25 | export interface Line extends Syntax<[]> { 26 | kind: 'line'; 27 | value: string; 28 | } 29 | 30 | export interface Group 31 | extends Syntax<[]> { 32 | kind: 'group'; 33 | start: Start; 34 | end: End; 35 | } 36 | 37 | export interface List extends Syntax<[]> { 38 | kind: 'list'; 39 | ofType: T; 40 | separator: S; 41 | } 42 | 43 | export interface SyntaxNode< 44 | Name extends string = string, 45 | Children extends SyntaxChild[] = SyntaxChild[], 46 | > extends Syntax { 47 | kind: 'node'; 48 | name: Name; 49 | } 50 | 51 | export interface Block 52 | extends Syntax { 53 | kind: 'block'; 54 | types: Types; 55 | end: string; 56 | start: string; 57 | default: Default; 58 | separator: string; 59 | } 60 | 61 | export interface Condition< 62 | Test extends string = string, 63 | Pass extends Syntax = Syntax, 64 | Fail extends Syntax = Syntax, 65 | > extends Syntax { 66 | kind: 'conditional'; 67 | test: Test; 68 | pass: Pass; 69 | fail: Fail; 70 | } 71 | --------------------------------------------------------------------------------