1// Copyright (c) 2020, Peter Ohler, All rights reserved. 2 3package alt 4 5import ( 6 "fmt" 7 8 "github.com/ohler55/ojg/gen" 9) 10 11var emptySlice = []interface{}{} 12 13// Builder is a basic type builder. It uses a stack model to build where maps 14// (objects) and slices (arrays) add pushed on the stack and closed with a 15// pop. 16type Builder struct { 17 stack []interface{} 18 starts []int 19} 20 21// Reset the builder. 22func (b *Builder) Reset() { 23 if 0 < cap(b.stack) && 0 < len(b.stack) { 24 b.stack = b.stack[:0] 25 b.starts = b.starts[:0] 26 } else { 27 b.stack = make([]interface{}, 0, 64) 28 b.starts = make([]int, 0, 16) 29 } 30} 31 32// Object pushs a map[string]interface{} onto the stack. A key must be 33// provided if the top of the stack is an object (map) and must not be 34// provided if the op of the stack is an array or slice. 35func (b *Builder) Object(key ...string) error { 36 newObj := map[string]interface{}{} 37 if 0 < len(key) { 38 if len(b.starts) == 0 || 0 <= b.starts[len(b.starts)-1] { 39 return fmt.Errorf("can not use a key when pushing to an array") 40 } 41 if obj, _ := b.stack[len(b.stack)-1].(map[string]interface{}); obj != nil { 42 obj[key[0]] = newObj 43 } 44 } else if 0 < len(b.starts) && b.starts[len(b.starts)-1] < 0 { 45 return fmt.Errorf("must have a key when pushing to an object") 46 } 47 b.starts = append(b.starts, -1) 48 b.stack = append(b.stack, newObj) 49 50 return nil 51} 52 53// Array pushs a []interface{} onto the stack. A key must be provided if the 54// top of the stack is an object (map) and must not be provided if the op of 55// the stack is an array or slice. 56func (b *Builder) Array(key ...string) error { 57 if 0 < len(key) { 58 if len(b.starts) == 0 || 0 <= b.starts[len(b.starts)-1] { 59 return fmt.Errorf("can not use a key when pushing to an array") 60 } 61 b.stack = append(b.stack, gen.Key(key[0])) 62 } else if 0 < len(b.starts) && b.starts[len(b.starts)-1] < 0 { 63 return fmt.Errorf("must have a key when pushing to an object") 64 } 65 b.starts = append(b.starts, len(b.stack)) 66 b.stack = append(b.stack, emptySlice) 67 68 return nil 69} 70 71// Value pushs a value onto the stack. A key must be provided if the top of 72// the stack is an object (map) and must not be provided if the op of the 73// stack is an array or slice. 74func (b *Builder) Value(value interface{}, key ...string) error { 75 if 0 < len(key) { 76 if len(b.starts) == 0 || 0 <= b.starts[len(b.starts)-1] { 77 return fmt.Errorf("can not use a key when pushing to an array") 78 } 79 if obj, _ := b.stack[len(b.stack)-1].(map[string]interface{}); obj != nil { 80 obj[key[0]] = value 81 } 82 } else if 0 < len(b.starts) && b.starts[len(b.starts)-1] < 0 { 83 return fmt.Errorf("must have a key when pushing to an object") 84 } else { 85 b.stack = append(b.stack, value) 86 } 87 return nil 88} 89 90// Pop the stack, closing an array or object. 91func (b *Builder) Pop() { 92 if 0 < len(b.starts) { 93 start := b.starts[len(b.starts)-1] 94 if 0 <= start { // array 95 start++ 96 size := len(b.stack) - start 97 a := make([]interface{}, size) 98 copy(a, b.stack[start:len(b.stack)]) 99 b.stack = b.stack[:start] 100 b.stack[start-1] = a 101 if 2 < len(b.stack) { 102 if k, ok := b.stack[len(b.stack)-2].(gen.Key); ok { 103 if obj, _ := b.stack[len(b.stack)-3].(map[string]interface{}); obj != nil { 104 obj[string(k)] = a 105 b.stack = b.stack[:len(b.stack)-2] 106 } 107 } 108 } 109 } 110 b.starts = b.starts[:len(b.starts)-1] 111 } 112} 113 114// PopAll repeats Pop until all open arrays or objects are closed. 115func (b *Builder) PopAll() { 116 for 0 < len(b.starts) { 117 b.Pop() 118 } 119} 120 121// Result of the builder is returned. This is the first item pushed on to the 122// stack. 123func (b *Builder) Result() (result interface{}) { 124 if 0 < len(b.stack) { 125 result = b.stack[0] 126 } 127 return 128} 129