-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathast.go
More file actions
165 lines (141 loc) · 5.12 KB
/
ast.go
File metadata and controls
165 lines (141 loc) · 5.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package sham
import (
"fmt"
"math/rand"
)
// Node represents a single element in the abstract syntax tree. A valid Sham
// AST must be able to generate data. As such, every node in tree must be
// implement the Generator interface. There are two main type of nodes: structural
// and terminal. Terminal nodes are leaves that generate values. Structural nodes
// generate the data structures that hold these values.
type Node interface {
Generator
}
// Schema represents a Sham schema and holds the root of the AST. The root of the
// AST must either be a single terminal node, or a structural node.
type Schema struct {
Root Node
}
// Generate triggers the Sham data generation process. The generation process
// begins with the root and walks the tree, generating data structures and data
// as it goes. Data generation cannot cause errors. Any possible errors will
// have been caught during the tokenization and parsing processes. This method
// can be called more than once to generate more data using the same schema.
func (s Schema) Generate() interface{} {
if s.Root == nil {
return nil
}
return s.Root.Generate()
}
// Object represents a key-value data structure. In order to maintain the key
// order in the schema, the pairs are stored in a slice and converted to an
// ordered map during the generation process. If a key is provided multiple
// times, the last value will be used during generation.
type Object struct {
Values []KV
}
// KV represents a single key-value pair in an Object.
type KV struct {
Key string
Value Node
}
// AppendPair adds a key-value pair to an Object.
func (m *Object) AppendPair(k string, v Node) {
m.Values = append(m.Values, KV{Key: k, Value: v})
}
// Generate creates a map of key-value pairs from the slice of KVs. An ordered
// map is used to preserve the order of the provided keys. If the same key is
// provided multiple times, then only the last value will be used.
func (m Object) Generate() interface{} {
out := NewOrderedMap()
for _, kv := range m.Values {
out.Set(kv.Key, kv.Value.Generate())
}
return out
}
// Array represents a list of values that can be generated. An array has two
// potential parts: an inner node and a range. The inner node defines the
// structure of the array elements, and the range defines how many elements
// should be present. The range is optional. If omitted, one element will be
// generated.
type Array struct {
Range *Range
Inner Node
}
// Generate creates a slice of generated values where each value is defined by
// the inner node field. If the range is omitted, then exactly one element will
// populate the array. Otherwise, a random number of elements will be generated
// based on the inclusive range of integers.
func (a Array) Generate() interface{} {
if a.Inner == nil {
return []interface{}{}
}
n := 1
if a.Range != nil {
n = a.Range.GetValue()
}
out := make([]interface{}, n)
for i := 0; i < n; i++ {
out[i] = a.Inner.Generate()
}
return out
}
// Range is an inclusive range of integers. Ranges have two uses within the a
// Sham schema. If provided as the first argument in an array, the range will
// be used to determine the number of elements to populate the array. If provided
// in the position of a terminal generator, then a random integer will be
// generated for the value.
type Range struct {
Min int
Max int
}
// GetValue retrieves a random integer from the inclusive range [min, max]. The
// chosen integer is not cryptographically secure and should never be treated
// as such.
func (r Range) GetValue() int {
if r.Min == r.Max {
return r.Min
}
return rand.Intn((r.Max+1)-r.Min) + r.Min
}
// Generate chooses a random integer from the inclusive range.
func (r Range) Generate() interface{} { return r.GetValue() }
// FormattedString represents a string literal with values that can be interpolated
// into the string. In the Sham language, formatted strings are enclosed in
// backticks, and the interpolated values are enclosed by curly braces. Interpolated
// values must be valid, registered terminal generators.
type FormattedString struct {
Raw string
Format string
Params []Generator
}
// Generate produces a string literal value by replacing interpolated values with
// the values generated by the corresponding terminal generator.
func (f FormattedString) Generate() interface{} {
if len(f.Params) == 0 {
return f.Raw
}
params := make([]interface{}, len(f.Params))
for i, p := range f.Params {
params[i] = p.Generate()
}
return fmt.Sprintf(f.Format, params...)
}
// TerminalGenerator represents a function that can generate data.
type TerminalGenerator struct {
Name string
fn Generator
}
// Generate runs the terminal generator's generation function. The generator is
// expected to be a non nil interface. If nil was registered for this terminal
// generator, then this method will panic.
func (t TerminalGenerator) Generate() interface{} {
return t.fn.Generate()
}
// Literal represents a literal value. No data generation is involved here, but
// rather values are returned as-is.
type Literal struct {
Value interface{}
}
// Generate returns the literal value.
func (l Literal) Generate() interface{} { return l.Value }