@@ -11,7 +11,12 @@ export interface UnknownT {
11
11
node : "Unknown" ;
12
12
}
13
13
14
- export type TypeNode = Base | MappingType | ArrayT | Obj | UnknownT ;
14
+ export interface EnumT {
15
+ node : 'Enum' ,
16
+ options : string [ ]
17
+ }
18
+
19
+ export type TypeNode = Base | MappingType | ArrayT | Obj | EnumT | UnknownT ;
15
20
16
21
export interface MappingType {
17
22
node : "MappingType" ;
@@ -37,17 +42,19 @@ export interface Obj {
37
42
// ---------------- Tokenizer ----------------
38
43
39
44
type TokKind =
40
- | "SQBRACKETS "
45
+ | "SQUARE_BRACKETS "
41
46
| "LBRACE"
42
47
| "RBRACE"
43
48
| "COLON"
44
49
| "SEMI"
45
- | "Q "
50
+ | "QUESTION_MARK "
46
51
| "COMMA"
47
- | "LT "
48
- | "GT "
52
+ | "LESS_THAN "
53
+ | "GREATER_THAN "
49
54
| "IDENT"
50
55
| "KW"
56
+ | "PIPE"
57
+ | "STRING_LITERAL"
51
58
| "UNKNOWN" ;
52
59
53
60
export interface Tok {
@@ -66,16 +73,17 @@ export function tokenize(src: string): Tok[] {
66
73
const c = s [ i ] ! ;
67
74
if ( / \s / . test ( c ) ) { i ++ ; continue ; }
68
75
if ( c === "[" && i + 1 < s . length && s [ i + 1 ] === "]" ) {
69
- out . push ( { kind : "SQBRACKETS " , value : "[]" , pos : i } ) ; i += 2 ; continue ;
76
+ out . push ( { kind : "SQUARE_BRACKETS " , value : "[]" , pos : i } ) ; i += 2 ; continue ;
70
77
}
71
78
if ( c === "{" ) { out . push ( { kind : "LBRACE" , value : c , pos : i } ) ; i ++ ; continue ; }
72
79
if ( c === "}" ) { out . push ( { kind : "RBRACE" , value : c , pos : i } ) ; i ++ ; continue ; }
73
80
if ( c === ":" ) { out . push ( { kind : "COLON" , value : c , pos : i } ) ; i ++ ; continue ; }
74
81
if ( c === ";" ) { out . push ( { kind : "SEMI" , value : c , pos : i } ) ; i ++ ; continue ; }
75
- if ( c === "?" ) { out . push ( { kind : "Q " , value : c , pos : i } ) ; i ++ ; continue ; }
82
+ if ( c === "?" ) { out . push ( { kind : "QUESTION_MARK " , value : c , pos : i } ) ; i ++ ; continue ; }
76
83
if ( c === "," ) { out . push ( { kind : "COMMA" , value : c , pos : i } ) ; i ++ ; continue ; }
77
- if ( c === "<" ) { out . push ( { kind : "LT" , value : c , pos : i } ) ; i ++ ; continue ; }
78
- if ( c === ">" ) { out . push ( { kind : "GT" , value : c , pos : i } ) ; i ++ ; continue ; }
84
+ if ( c === "<" ) { out . push ( { kind : "LESS_THAN" , value : c , pos : i } ) ; i ++ ; continue ; }
85
+ if ( c === ">" ) { out . push ( { kind : "GREATER_THAN" , value : c , pos : i } ) ; i ++ ; continue ; }
86
+ if ( c === '|' ) { out . push ( { kind : "PIPE" , value : c , pos : i } ) ; i ++ ; continue ; }
79
87
80
88
if ( / [ A - Z a - z _ ] / . test ( c ) ) {
81
89
let j = i + 1 ;
@@ -85,6 +93,35 @@ export function tokenize(src: string): Tok[] {
85
93
out . push ( { kind, value : word , pos : i } ) ;
86
94
i = j ;
87
95
continue ;
96
+ } else if ( c === "'" || c === '"' ) {
97
+ const quote = c
98
+ let j = i + 1
99
+ let buf = ""
100
+ while ( j < s . length ) {
101
+ const ch = s [ j ]
102
+
103
+ if ( ch === "\\" && j + 1 < s . length ) {
104
+ buf += s [ j + 1 ]
105
+ j += 2
106
+ continue
107
+ }
108
+
109
+ if ( ch === quote ) {
110
+ j ++
111
+ break
112
+ }
113
+
114
+ buf += ch
115
+ j ++
116
+ }
117
+
118
+ if ( j > s . length ) {
119
+ throw new SyntaxError ( `Unterminated string starting at ${ i } ` )
120
+ }
121
+
122
+ out . push ( { kind : "STRING_LITERAL" , value : buf , pos : i } )
123
+ i = j
124
+ continue
88
125
} else {
89
126
90
127
// Scope for improvement
@@ -161,7 +198,7 @@ class Parser {
161
198
// Parse the field name
162
199
private parse_field ( ) : Field {
163
200
const name_tok = this . want ( "IDENT" ) ;
164
- const optional = ! ! this . accept ( "Q " ) ;
201
+ const optional = ! ! this . accept ( "QUESTION_MARK " ) ;
165
202
this . want ( "COLON" ) ;
166
203
const typ = this . parse_type ( ) ;
167
204
return { name : name_tok . value , typ, optional } ;
@@ -170,7 +207,7 @@ class Parser {
170
207
// Parse the field type
171
208
private parse_type ( ) : TypeNode {
172
209
let t = this . parse_primary ( ) ;
173
- while ( this . accept ( "SQBRACKETS " ) ) {
210
+ while ( this . accept ( "SQUARE_BRACKETS " ) ) {
174
211
t = { node : "Array" , item : t } ;
175
212
}
176
213
return t ;
@@ -180,6 +217,21 @@ class Parser {
180
217
const t = this . peek ( ) ;
181
218
if ( ! t ) throw new SyntaxError ( "Unexpected EOF while parsing Type" ) ;
182
219
220
+ if ( t . kind === 'STRING_LITERAL' ) {
221
+
222
+ const options : string [ ] = [ ]
223
+ options . push ( this . want ( "STRING_LITERAL" ) . value )
224
+ while ( this . accept ( "PIPE" ) ) {
225
+
226
+ if ( this . peek ( ) ?. kind !== 'STRING_LITERAL' )
227
+ throw new SyntaxError ( `Exprected String Literal at position: ${ this . peek ( ) ?. pos ?? "EOF" } ` )
228
+
229
+ options . push ( this . want ( "STRING_LITERAL" ) . value )
230
+ }
231
+
232
+ return { node : "Enum" , options : options }
233
+ }
234
+
183
235
// Object or '{}'
184
236
if ( t . kind === "LBRACE" ) {
185
237
if ( this . peek ( 1 ) && this . peek ( 1 ) ! . kind === "RBRACE" ) {
@@ -192,13 +244,13 @@ class Parser {
192
244
// Record<string, T>
193
245
if ( t . kind === "IDENT" && t . value === "Record" ) {
194
246
this . want ( "IDENT" ) ; // Record
195
- this . want ( "LT " ) ; // <
247
+ this . want ( "LESS_THAN " ) ; // <
196
248
const k = this . want ( "KW" ) ; // expect 'string'
197
249
if ( k . value !== "string" )
198
250
throw new SyntaxError ( `Only Record<string, ...> supported at pos ${ k . pos } ` ) ;
199
251
this . want ( "COMMA" ) ;
200
252
const val = this . parse_type ( ) ;
201
- this . want ( "GT " ) ;
253
+ this . want ( "GREATER_THAN " ) ;
202
254
return { node : "MappingType" , value : val } ;
203
255
}
204
256
@@ -244,19 +296,33 @@ export type Sig =
244
296
| [ "array" , Sig ]
245
297
| [ "obj" , ReadonlyArray < [ string , boolean , Sig ] > ]
246
298
| [ "mapping" , Sig ]
299
+ | [ "enum" , string [ ] ]
247
300
| [ "unknown" ] ;
248
301
249
302
export function type_signature ( t : TypeNode ) : Sig {
250
- if ( t . node === "Base" ) return [ "base" , t . kind ] ;
251
- if ( t . node === "Array" ) return [ "array" , type_signature ( t . item ) ] ;
303
+ if ( t . node === "Base" )
304
+ return [ "base" , t . kind ] ;
305
+
306
+ if ( t . node === "Array" )
307
+ return [ "array" , type_signature ( t . item ) ] ;
308
+
252
309
if ( t . node === "Obj" ) {
253
310
const items = [ ...t . fields ]
254
311
. map ( f => [ f . name , f . optional , type_signature ( f . typ ) ] as [ string , boolean , Sig ] )
255
312
. sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] ) ) ;
256
313
return [ "obj" , items ] ;
257
314
}
258
- if ( t . node === "MappingType" ) return [ "mapping" , type_signature ( t . value ) ] ;
259
- if ( t . node == "Unknown" ) return [ "unknown" ]
315
+
316
+ if ( t . node === "MappingType" )
317
+ return [ "mapping" , type_signature ( t . value ) ] ;
318
+
319
+ if ( t . node == "Unknown" )
320
+ return [ "unknown" ]
321
+
322
+ if ( t . node === "Enum" ) {
323
+ const opts = [ ...t . options ] . sort ( ) ;
324
+ return [ "enum" , opts ] ;
325
+ }
260
326
throw new TypeError ( `Unknown TypeNode: ${ ( t as any ) && ( t as any ) . node } ` ) ;
261
327
}
262
328
@@ -364,6 +430,11 @@ export function py_type(t: TypeNode, name_of: Map<string, string>): string {
364
430
return `Mapping[str, ${ inner } ]` ;
365
431
}
366
432
433
+ if ( t . node === "Enum" ) {
434
+ const opts = t . options . map ( o => JSON . stringify ( o ) ) . join ( ", " ) ;
435
+ return `Literal[${ opts } ]` ;
436
+ }
437
+
367
438
if ( t . node === "Unknown" ) {
368
439
return "Any" ;
369
440
}
0 commit comments