@@ -18,7 +18,6 @@ package markers
18
18
19
19
import (
20
20
"bytes"
21
- "errors"
22
21
"fmt"
23
22
"reflect"
24
23
"strconv"
@@ -40,6 +39,15 @@ func expect(scanner *sc.Scanner, expected rune, errDesc string) bool {
40
39
return true
41
40
}
42
41
42
+ // peekNoSpace is equivalent to scanner.Peek, except that it will consume intervening whitespace.
43
+ func peekNoSpace (scanner * sc.Scanner ) rune {
44
+ hint := scanner .Peek ()
45
+ for ; hint <= rune (' ' ) && ((1 << uint64 (hint ))& scanner .Whitespace ) != 0 ; hint = scanner .Peek () {
46
+ scanner .Next () // skip the whitespace
47
+ }
48
+ return hint
49
+ }
50
+
43
51
var (
44
52
// interfaceType is a pre-computed reflect.Type representing the empty interface.
45
53
interfaceType = reflect .TypeOf ((* interface {})(nil )).Elem ()
@@ -84,17 +92,27 @@ const (
84
92
AnyType
85
93
// SliceType is any slice constructed of the ArgumentTypes
86
94
SliceType
95
+ // MapType is any map constructed of string keys, and ArgumentType values.
96
+ // Keys are strings, and it's common to see AnyType (non-uniform) values.
97
+ MapType
87
98
// RawType represents content that gets passed directly to the marker
88
99
// without any parsing. It should *only* be used with anonymous markers.
89
100
RawType
90
101
)
91
102
92
103
// Argument is the type of a marker argument.
93
104
type Argument struct {
94
- Type ArgumentType
105
+ // Type is the type of this argument For non-scalar types (map and slice),
106
+ // further information is specified in ItemType.
107
+ Type ArgumentType
108
+ // Optional indicates if this argument is optional.
95
109
Optional bool
96
- Pointer bool
110
+ // Pointer indicates if this argument was a pointer (this is really only
111
+ // needed for deserialization, and should alway imply optional)
112
+ Pointer bool
97
113
114
+ // ItemType is the type of the slice item for slices, and the value type
115
+ // for maps.
98
116
ItemType * Argument
99
117
}
100
118
@@ -119,6 +137,9 @@ func (a Argument) typeString(out *strings.Builder) {
119
137
out .WriteString ("[]" )
120
138
// arguments can't be non-pointer optional, so just call into typeString again.
121
139
a .ItemType .typeString (out )
140
+ case MapType :
141
+ out .WriteString ("map[string]" )
142
+ a .ItemType .typeString (out )
122
143
case RawType :
123
144
out .WriteString ("<raw>" )
124
145
}
@@ -169,6 +190,13 @@ func makeSliceType(itemType Argument) (reflect.Type, error) {
169
190
return nil , err
170
191
}
171
192
itemReflectedType = subItemType
193
+ case MapType :
194
+ subItemType , err := makeMapType (* itemType .ItemType )
195
+ if err != nil {
196
+ return nil , err
197
+ }
198
+ itemReflectedType = subItemType
199
+ // TODO(directxman12): support non-uniform slices? (probably not)
172
200
default :
173
201
return nil , fmt .Errorf ("invalid type when constructing guessed slice out: %v" , itemType .Type )
174
202
}
@@ -180,10 +208,50 @@ func makeSliceType(itemType Argument) (reflect.Type, error) {
180
208
return reflect .SliceOf (itemReflectedType ), nil
181
209
}
182
210
211
+ // makeMapType makes a reflect.Type for a map of the given item type.
212
+ // Useful for constructing the out value for when AnyType's guess returns a map.
213
+ func makeMapType (itemType Argument ) (reflect.Type , error ) {
214
+ var itemReflectedType reflect.Type
215
+ switch itemType .Type {
216
+ case IntType :
217
+ itemReflectedType = reflect .TypeOf (int (0 ))
218
+ case StringType :
219
+ itemReflectedType = reflect .TypeOf ("" )
220
+ case BoolType :
221
+ itemReflectedType = reflect .TypeOf (false )
222
+ case SliceType :
223
+ subItemType , err := makeSliceType (* itemType .ItemType )
224
+ if err != nil {
225
+ return nil , err
226
+ }
227
+ itemReflectedType = subItemType
228
+ // TODO(directxman12): support non-uniform slices? (probably not)
229
+ case MapType :
230
+ subItemType , err := makeMapType (* itemType .ItemType )
231
+ if err != nil {
232
+ return nil , err
233
+ }
234
+ itemReflectedType = subItemType
235
+ case AnyType :
236
+ // NB(directxman12): maps explicitly allow non-uniform item types, unlike slices at the moment
237
+ itemReflectedType = interfaceType
238
+ default :
239
+ return nil , fmt .Errorf ("invalid type when constructing guessed slice out: %v" , itemType .Type )
240
+ }
241
+
242
+ if itemType .Pointer {
243
+ itemReflectedType = reflect .PtrTo (itemReflectedType )
244
+ }
245
+
246
+ return reflect .MapOf (reflect .TypeOf ("" ), itemReflectedType ), nil
247
+ }
248
+
183
249
// guessType takes an educated guess about the type of the next field. If allowSlice
184
250
// is false, it will not guess slices. It's less efficient than parsing with actual
185
251
// type information, since we need to allocate to peek ahead full tokens, and the scanner
186
252
// only allows peeking ahead one character.
253
+ // Maps are *always* non-uniform (i.e. type the AnyType item type), since they're frequently
254
+ // used to represent things like defaults for an object in JSON.
187
255
func guessType (scanner * sc.Scanner , raw string , allowSlice bool ) * Argument {
188
256
if allowSlice {
189
257
maybeItem := guessType (scanner , raw , false )
@@ -207,25 +275,49 @@ func guessType(scanner *sc.Scanner, raw string, allowSlice bool) *Argument {
207
275
return maybeItem
208
276
}
209
277
210
- // first, try the easy case -- quoted strings strings
211
- hint := scanner .Peek ()
212
- switch hint {
213
- case '"' , '\'' , '`' :
214
- return & Argument {Type : StringType }
215
- }
216
-
217
278
// everything else needs a duplicate scanner to scan properly
218
279
// (so we don't consume our scanner tokens until we actually
219
280
// go to use this -- Go doesn't like scanners that can be rewound).
220
281
subRaw := raw [scanner .Pos ().Offset :]
221
282
subScanner := parserScanner (subRaw , scanner .Error )
222
283
223
- // next, check for slices
284
+ // skip whitespace
285
+ hint := peekNoSpace (subScanner )
286
+
287
+ // first, try the easy case -- quoted strings strings
288
+ switch hint {
289
+ case '"' , '\'' , '`' :
290
+ return & Argument {Type : StringType }
291
+ }
292
+
293
+ // next, check for slices or maps
224
294
if hint == '{' {
225
295
subScanner .Scan ()
296
+
297
+ // TODO(directxman12): this can't guess at empty objects, but that's generally ok.
298
+ // We'll cross that bridge when we get there.
299
+
300
+ // look ahead till we can figure out if this is a map or a slice
301
+ firstElemType := guessType (subScanner , subRaw , false )
302
+ if firstElemType .Type == StringType {
303
+ // might be a map or slice, parse the string and check for colon
304
+ // (blech, basically arbitrary look-ahead due to raw strings).
305
+ var keyVal string // just ignore this
306
+ (& Argument {Type : StringType }).parseString (subScanner , raw , reflect .Indirect (reflect .ValueOf (& keyVal )))
307
+
308
+ if subScanner .Scan () == ':' {
309
+ // it's got a string followed by a colon -- it's a map
310
+ return & Argument {
311
+ Type : MapType ,
312
+ ItemType : & Argument {Type : AnyType },
313
+ }
314
+ }
315
+ }
316
+
317
+ // definitely a slice -- maps have to have string keys and have a value followed by a colon
226
318
return & Argument {
227
319
Type : SliceType ,
228
- ItemType : guessType ( subScanner , subRaw , false ) ,
320
+ ItemType : firstElemType ,
229
321
}
230
322
}
231
323
@@ -276,10 +368,10 @@ func (a *Argument) parseString(scanner *sc.Scanner, raw string, out reflect.Valu
276
368
}
277
369
278
370
// the "hard" case -- bare tokens not including ',' (the argument
279
- // separator), ';' (the slice separator), or '} ' (delimitted slice
280
- // ender)
371
+ // separator), ';' (the slice separator), ': ' (the map separator), or '}'
372
+ // (delimitted slice ender)
281
373
startPos := scanner .Position .Offset
282
- for hint := scanner . Peek ( ); hint != ',' && hint != ';' && hint != '}' && hint != sc .EOF ; hint = scanner . Peek ( ) {
374
+ for hint := peekNoSpace ( scanner ); hint != ',' && hint != ';' && hint != ':' && hint != ' }' && hint != sc .EOF ; hint = peekNoSpace ( scanner ) {
283
375
// skip this token
284
376
scanner .Scan ()
285
377
}
@@ -296,15 +388,15 @@ func (a *Argument) parseSlice(scanner *sc.Scanner, raw string, out reflect.Value
296
388
elem := reflect .Indirect (reflect .New (out .Type ().Elem ()))
297
389
298
390
// preferred case
299
- if scanner . Peek ( ) == '{' {
391
+ if peekNoSpace ( scanner ) == '{' {
300
392
// NB(directxman12): supporting delimitted slices in bare slices
301
393
// would require an extra look-ahead here :-/
302
394
303
395
scanner .Scan () // skip '{'
304
- for hint := scanner . Peek ( ); hint != '}' && hint != sc .EOF ; hint = scanner . Peek ( ) {
305
- a .ItemType .parse (scanner , raw , elem , true )
396
+ for hint := peekNoSpace ( scanner ); hint != '}' && hint != sc .EOF ; hint = peekNoSpace ( scanner ) {
397
+ a .ItemType .parse (scanner , raw , elem , true /* parsing a slice */ )
306
398
resSlice = reflect .Append (resSlice , elem )
307
- tok := scanner . Peek ( )
399
+ tok := peekNoSpace ( scanner )
308
400
if tok == '}' {
309
401
break
310
402
}
@@ -320,10 +412,10 @@ func (a *Argument) parseSlice(scanner *sc.Scanner, raw string, out reflect.Value
320
412
}
321
413
322
414
// legacy case
323
- for hint := scanner . Peek ( ); hint != ',' && hint != '}' && hint != sc .EOF ; hint = scanner . Peek ( ) {
324
- a .ItemType .parse (scanner , raw , elem , true )
415
+ for hint := peekNoSpace ( scanner ); hint != ',' && hint != '}' && hint != sc .EOF ; hint = peekNoSpace ( scanner ) {
416
+ a .ItemType .parse (scanner , raw , elem , true /* parsing a slice */ )
325
417
resSlice = reflect .Append (resSlice , elem )
326
- tok := scanner . Peek ( )
418
+ tok := peekNoSpace ( scanner )
327
419
if tok == ',' || tok == '}' || tok == sc .EOF {
328
420
break
329
421
}
@@ -336,6 +428,39 @@ func (a *Argument) parseSlice(scanner *sc.Scanner, raw string, out reflect.Value
336
428
castAndSet (out , resSlice )
337
429
}
338
430
431
+ // parseMap parses a map of the form {string: val, string: val, string: val}
432
+ func (a * Argument ) parseMap (scanner * sc.Scanner , raw string , out reflect.Value ) {
433
+ resMap := reflect .MakeMap (out .Type ())
434
+ elem := reflect .Indirect (reflect .New (out .Type ().Elem ()))
435
+ key := reflect .Indirect (reflect .New (out .Type ().Key ()))
436
+
437
+ if ! expect (scanner , '{' , "open curly brace" ) {
438
+ return
439
+ }
440
+
441
+ for hint := peekNoSpace (scanner ); hint != '}' && hint != sc .EOF ; hint = peekNoSpace (scanner ) {
442
+ a .parseString (scanner , raw , key )
443
+ if ! expect (scanner , ':' , "colon" ) {
444
+ return
445
+ }
446
+ a .ItemType .parse (scanner , raw , elem , false /* not in a slice */ )
447
+ resMap .SetMapIndex (key , elem )
448
+
449
+ if peekNoSpace (scanner ) == '}' {
450
+ break
451
+ }
452
+ if ! expect (scanner , ',' , "comma" ) {
453
+ return
454
+ }
455
+ }
456
+
457
+ if ! expect (scanner , '}' , "close curly brace" ) {
458
+ return
459
+ }
460
+
461
+ castAndSet (out , resMap )
462
+ }
463
+
339
464
// parse functions like Parse, except that it allows passing down whether or not we're
340
465
// already in a slice, to avoid duplicate legacy slice detection for AnyType
341
466
func (a * Argument ) parse (scanner * sc.Scanner , raw string , out reflect.Value , inSlice bool ) {
@@ -387,18 +512,27 @@ func (a *Argument) parse(scanner *sc.Scanner, raw string, out reflect.Value, inS
387
512
case AnyType :
388
513
guessedType := guessType (scanner , raw , ! inSlice )
389
514
newOut := out
390
- if guessedType .Type == SliceType {
391
- // we need to be able to construct the right element types, below
392
- // in parse, so construct a concretely-typed value to use as "out"
515
+
516
+ // we need to be able to construct the right element types, below
517
+ // in parse, so construct a concretely-typed value to use as "out"
518
+ switch guessedType .Type {
519
+ case SliceType :
393
520
newType , err := makeSliceType (* guessedType .ItemType )
394
521
if err != nil {
395
522
scanner .Error (scanner , err .Error ())
396
523
return
397
524
}
398
525
newOut = reflect .Indirect (reflect .New (newType ))
526
+ case MapType :
527
+ newType , err := makeMapType (* guessedType .ItemType )
528
+ if err != nil {
529
+ scanner .Error (scanner , err .Error ())
530
+ return
531
+ }
532
+ newOut = reflect .Indirect (reflect .New (newType ))
399
533
}
400
534
if ! newOut .CanSet () {
401
- panic ("at the disco" )
535
+ panic ("at the disco" ) // TODO(directxman12): this is left over from debugging -- it might need to be an error
402
536
}
403
537
guessedType .Parse (scanner , raw , newOut )
404
538
castAndSet (out , newOut )
@@ -407,6 +541,9 @@ func (a *Argument) parse(scanner *sc.Scanner, raw string, out reflect.Value, inS
407
541
// - `{val, val, val}` (preferred)
408
542
// - `val;val;val` (legacy)
409
543
a .parseSlice (scanner , raw , out )
544
+ case MapType :
545
+ // maps are {string: val, string: val, string: val}
546
+ a .parseMap (scanner , raw , out )
410
547
}
411
548
}
412
549
@@ -454,6 +591,16 @@ func ArgumentFromType(rawType reflect.Type) (Argument, error) {
454
591
return Argument {}, fmt .Errorf ("bad slice item type: %v" , err )
455
592
}
456
593
arg .ItemType = & itemType
594
+ case reflect .Map :
595
+ arg .Type = MapType
596
+ if rawType .Key ().Kind () != reflect .String {
597
+ return Argument {}, fmt .Errorf ("bad map key type: map keys must be strings" )
598
+ }
599
+ itemType , err := ArgumentFromType (rawType .Elem ())
600
+ if err != nil {
601
+ return Argument {}, fmt .Errorf ("bad slice item type: %v" , err )
602
+ }
603
+ arg .ItemType = & itemType
457
604
default :
458
605
return Argument {}, fmt .Errorf ("type has unsupported kind %s" , rawType .Kind ())
459
606
}
@@ -614,8 +761,8 @@ func (d *Definition) Parse(rawMarker string) (interface{}, error) {
614
761
}
615
762
616
763
var errs []error
617
- scanner := parserScanner (fields , func (_ * sc.Scanner , msg string ) {
618
- errs = append (errs , errors . New ( msg ) )
764
+ scanner := parserScanner (fields , func (scanner * sc.Scanner , msg string ) {
765
+ errs = append (errs , & ScannerError { Msg : msg , Pos : scanner . Position } )
619
766
})
620
767
621
768
// TODO(directxman12): strict parsing where we error out if certain fields aren't optional
@@ -725,3 +872,12 @@ func splitMarker(raw string) (name string, anonymousName string, restFields stri
725
872
}
726
873
return name , anonymousName , restFields
727
874
}
875
+
876
+ type ScannerError struct {
877
+ Msg string
878
+ Pos sc.Position
879
+ }
880
+
881
+ func (e * ScannerError ) Error () string {
882
+ return fmt .Sprintf ("%s (at %s)" , e .Msg , e .Pos )
883
+ }
0 commit comments