Skip to content

Commit 64b0534

Browse files
authored
Merge pull request #329 from DirectXMan12/prototype/object-parsing
support maps in marker marker parsing
2 parents 76a25b6 + 57ecfbd commit 64b0534

File tree

2 files changed

+227
-31
lines changed

2 files changed

+227
-31
lines changed

pkg/markers/parse.go

Lines changed: 197 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package markers
1818

1919
import (
2020
"bytes"
21-
"errors"
2221
"fmt"
2322
"reflect"
2423
"strconv"
@@ -40,6 +39,15 @@ func expect(scanner *sc.Scanner, expected rune, errDesc string) bool {
4039
return true
4140
}
4241

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+
4351
var (
4452
// interfaceType is a pre-computed reflect.Type representing the empty interface.
4553
interfaceType = reflect.TypeOf((*interface{})(nil)).Elem()
@@ -84,17 +92,27 @@ const (
8492
AnyType
8593
// SliceType is any slice constructed of the ArgumentTypes
8694
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
8798
// RawType represents content that gets passed directly to the marker
8899
// without any parsing. It should *only* be used with anonymous markers.
89100
RawType
90101
)
91102

92103
// Argument is the type of a marker argument.
93104
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.
95109
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
97113

114+
// ItemType is the type of the slice item for slices, and the value type
115+
// for maps.
98116
ItemType *Argument
99117
}
100118

@@ -119,6 +137,9 @@ func (a Argument) typeString(out *strings.Builder) {
119137
out.WriteString("[]")
120138
// arguments can't be non-pointer optional, so just call into typeString again.
121139
a.ItemType.typeString(out)
140+
case MapType:
141+
out.WriteString("map[string]")
142+
a.ItemType.typeString(out)
122143
case RawType:
123144
out.WriteString("<raw>")
124145
}
@@ -169,6 +190,13 @@ func makeSliceType(itemType Argument) (reflect.Type, error) {
169190
return nil, err
170191
}
171192
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)
172200
default:
173201
return nil, fmt.Errorf("invalid type when constructing guessed slice out: %v", itemType.Type)
174202
}
@@ -180,10 +208,50 @@ func makeSliceType(itemType Argument) (reflect.Type, error) {
180208
return reflect.SliceOf(itemReflectedType), nil
181209
}
182210

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+
183249
// guessType takes an educated guess about the type of the next field. If allowSlice
184250
// is false, it will not guess slices. It's less efficient than parsing with actual
185251
// type information, since we need to allocate to peek ahead full tokens, and the scanner
186252
// 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.
187255
func guessType(scanner *sc.Scanner, raw string, allowSlice bool) *Argument {
188256
if allowSlice {
189257
maybeItem := guessType(scanner, raw, false)
@@ -207,25 +275,49 @@ func guessType(scanner *sc.Scanner, raw string, allowSlice bool) *Argument {
207275
return maybeItem
208276
}
209277

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-
217278
// everything else needs a duplicate scanner to scan properly
218279
// (so we don't consume our scanner tokens until we actually
219280
// go to use this -- Go doesn't like scanners that can be rewound).
220281
subRaw := raw[scanner.Pos().Offset:]
221282
subScanner := parserScanner(subRaw, scanner.Error)
222283

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
224294
if hint == '{' {
225295
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
226318
return &Argument{
227319
Type: SliceType,
228-
ItemType: guessType(subScanner, subRaw, false),
320+
ItemType: firstElemType,
229321
}
230322
}
231323

@@ -276,10 +368,10 @@ func (a *Argument) parseString(scanner *sc.Scanner, raw string, out reflect.Valu
276368
}
277369

278370
// 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)
281373
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) {
283375
// skip this token
284376
scanner.Scan()
285377
}
@@ -296,15 +388,15 @@ func (a *Argument) parseSlice(scanner *sc.Scanner, raw string, out reflect.Value
296388
elem := reflect.Indirect(reflect.New(out.Type().Elem()))
297389

298390
// preferred case
299-
if scanner.Peek() == '{' {
391+
if peekNoSpace(scanner) == '{' {
300392
// NB(directxman12): supporting delimitted slices in bare slices
301393
// would require an extra look-ahead here :-/
302394

303395
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 */)
306398
resSlice = reflect.Append(resSlice, elem)
307-
tok := scanner.Peek()
399+
tok := peekNoSpace(scanner)
308400
if tok == '}' {
309401
break
310402
}
@@ -320,10 +412,10 @@ func (a *Argument) parseSlice(scanner *sc.Scanner, raw string, out reflect.Value
320412
}
321413

322414
// 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 */)
325417
resSlice = reflect.Append(resSlice, elem)
326-
tok := scanner.Peek()
418+
tok := peekNoSpace(scanner)
327419
if tok == ',' || tok == '}' || tok == sc.EOF {
328420
break
329421
}
@@ -336,6 +428,39 @@ func (a *Argument) parseSlice(scanner *sc.Scanner, raw string, out reflect.Value
336428
castAndSet(out, resSlice)
337429
}
338430

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+
339464
// parse functions like Parse, except that it allows passing down whether or not we're
340465
// already in a slice, to avoid duplicate legacy slice detection for AnyType
341466
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
387512
case AnyType:
388513
guessedType := guessType(scanner, raw, !inSlice)
389514
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:
393520
newType, err := makeSliceType(*guessedType.ItemType)
394521
if err != nil {
395522
scanner.Error(scanner, err.Error())
396523
return
397524
}
398525
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))
399533
}
400534
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
402536
}
403537
guessedType.Parse(scanner, raw, newOut)
404538
castAndSet(out, newOut)
@@ -407,6 +541,9 @@ func (a *Argument) parse(scanner *sc.Scanner, raw string, out reflect.Value, inS
407541
// - `{val, val, val}` (preferred)
408542
// - `val;val;val` (legacy)
409543
a.parseSlice(scanner, raw, out)
544+
case MapType:
545+
// maps are {string: val, string: val, string: val}
546+
a.parseMap(scanner, raw, out)
410547
}
411548
}
412549

@@ -454,6 +591,16 @@ func ArgumentFromType(rawType reflect.Type) (Argument, error) {
454591
return Argument{}, fmt.Errorf("bad slice item type: %v", err)
455592
}
456593
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
457604
default:
458605
return Argument{}, fmt.Errorf("type has unsupported kind %s", rawType.Kind())
459606
}
@@ -614,16 +761,28 @@ func (d *Definition) Parse(rawMarker string) (interface{}, error) {
614761
}
615762

616763
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})
619766
})
620767

621768
// TODO(directxman12): strict parsing where we error out if certain fields aren't optional
622769
seen := make(map[string]struct{}, len(d.Fields))
623770
if d.AnonymousField() && scanner.Peek() != sc.EOF {
771+
// might still be a struct that something fiddled with, so double check
772+
structFieldName := d.FieldNames[""]
773+
outTarget := out
774+
if structFieldName != "" {
775+
// it's a struct field mapped to an anonymous marker
776+
outTarget = out.FieldByName(structFieldName)
777+
if !outTarget.CanSet() {
778+
scanner.Error(scanner, fmt.Sprintf("cannot set field %q (might not exist)", structFieldName))
779+
return out.Interface(), loader.MaybeErrList(errs)
780+
}
781+
}
782+
624783
// no need for trying to parse field names if we're not a struct
625784
field := d.Fields[""]
626-
field.Parse(scanner, fields, out)
785+
field.Parse(scanner, fields, outTarget)
627786
seen[""] = struct{}{} // mark as seen for strict definitions
628787
} else if !d.Empty() && scanner.Peek() != sc.EOF {
629788
// if we expect *and* actually have arguments passed
@@ -725,3 +884,12 @@ func splitMarker(raw string) (name string, anonymousName string, restFields stri
725884
}
726885
return name, anonymousName, restFields
727886
}
887+
888+
type ScannerError struct {
889+
Msg string
890+
Pos sc.Position
891+
}
892+
893+
func (e *ScannerError) Error() string {
894+
return fmt.Sprintf("%s (at %s)", e.Msg, e.Pos)
895+
}

0 commit comments

Comments
 (0)