Skip to content

Commit 39d5a90

Browse files
committed
convert parse tree to interior types
1 parent 9e60071 commit 39d5a90

File tree

3 files changed

+272
-334
lines changed

3 files changed

+272
-334
lines changed

internal/qmp-gen/main.go

Lines changed: 261 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
package gen
1616

1717
import (
18+
"container/list"
1819
"flag"
1920
"go/format"
2021
"io/ioutil"
22+
23+
qapischema "github.com/digitalocean/go-qemu/qapi-schema"
2124
)
2225

2326
const specURL = `https://raw.githubusercontent.com/qemu/qemu/stable-2.11/qapi-schema.json`
@@ -32,16 +35,23 @@ var (
3235
func Generate() error {
3336
flag.Parse()
3437

35-
defs, err := readDefinitions(*inputSpec)
38+
src, err := getQAPI(*inputSpec)
3639
if err != nil {
3740
return err
3841
}
3942

40-
// Generate and write out the code.
41-
symbols, err := parse(defs)
43+
tree, err := qapischema.Parse(string(src))
4244
if err != nil {
4345
return err
4446
}
47+
48+
tree, err = completeParseTree(tree, *inputSpec)
49+
if err != nil {
50+
return err
51+
}
52+
53+
symbols := lowerParseTree(tree)
54+
4555
need := neededTypes(symbols)
4656
bs, err := renderAPI(*templates, symbols, need)
4757
if err != nil {
@@ -57,3 +67,251 @@ func Generate() error {
5767

5868
return ioutil.WriteFile(*outputGo, formatted, 0640)
5969
}
70+
71+
// completeParseTree walks the parse tree and expands the Include statements.
72+
// It returns the completely resolved parse tree.
73+
func completeParseTree(tree *qapischema.Tree, base string) (*qapischema.Tree, error) {
74+
if inc, ok := tree.Node.(qapischema.Include); ok {
75+
src, err := resolvePath(base, string(inc))
76+
if err != nil {
77+
return nil, err
78+
}
79+
80+
// Any include directives in a subtree of this node will use
81+
// *this* include directive as its main path.
82+
base = src
83+
84+
srcBytes, err := getQAPI(src)
85+
if err != nil {
86+
return nil, err
87+
}
88+
89+
root, err := qapischema.Parse(string(srcBytes))
90+
if err != nil {
91+
return nil, err
92+
}
93+
94+
tree.Children = root.Children
95+
}
96+
97+
for i, subtree := range tree.Children {
98+
st, err := completeParseTree(subtree, base)
99+
if err != nil {
100+
return tree, err
101+
}
102+
103+
tree.Children[i] = st
104+
}
105+
106+
return tree, nil
107+
}
108+
109+
// lowerParseTree converts the parsed types from package qapischema into
110+
// types that are wired up for Go code generation.
111+
func lowerParseTree(tree *qapischema.Tree) map[name]interface{} {
112+
types := make(map[name]interface{})
113+
114+
remaining := list.New()
115+
remaining.PushFront(tree)
116+
117+
for remaining.Len() > 0 {
118+
tree := remaining.Remove(remaining.Front()).(*qapischema.Tree)
119+
120+
for _, t := range tree.Children {
121+
remaining.PushBack(t)
122+
}
123+
124+
switch node := tree.Node.(type) {
125+
case qapischema.Root:
126+
// Nothing to do, subtrees were already added to the queue above.
127+
case qapischema.Include:
128+
// Nothing to do, subtrees were already added to the queue above.
129+
case qapischema.Pragma:
130+
// Ignored by go-qemu.
131+
case *qapischema.Struct:
132+
v := structType{
133+
Name: name(node.Name),
134+
Base: name(node.Base),
135+
}
136+
137+
for _, member := range node.Members {
138+
f := field{
139+
Name: name(member.Name),
140+
Optional: member.Optional,
141+
}
142+
143+
if member.Type.TypeArray != "" {
144+
f.Type = name(member.Type.TypeArray)
145+
f.List = true
146+
} else {
147+
f.Type = name(member.Type.Type)
148+
}
149+
v.Fields = append(v.Fields, f)
150+
}
151+
152+
types[v.Name] = v
153+
154+
case *qapischema.Command:
155+
v := command{
156+
Name: name(node.Name),
157+
}
158+
159+
if node.Boxed != nil {
160+
v.BoxedInput = *node.Boxed
161+
}
162+
163+
if node.Data.Ref != nil {
164+
v.Inputs.Ref = name(*node.Data.Ref)
165+
} else if node.Data.Embed != nil {
166+
var fs fields
167+
for _, member := range node.Data.Embed.Members {
168+
f := field{
169+
Name: name(member.Name),
170+
Optional: member.Optional,
171+
}
172+
173+
if member.Type.TypeArray != "" {
174+
f.List = true
175+
f.Type = name(member.Type.TypeArray)
176+
} else {
177+
f.Type = name(member.Type.Type)
178+
}
179+
180+
fs = append(fs, f)
181+
v.Inputs.AnonFields = fs
182+
}
183+
}
184+
185+
if node.Returns != nil {
186+
// TODO: Output.Name is confusing
187+
if node.Returns.TypeArray != "" {
188+
v.Output = field{
189+
Name: name(node.Returns.TypeArray),
190+
List: true,
191+
Type: name(node.Returns.TypeArray),
192+
// TODO: Optional:
193+
}
194+
} else {
195+
v.Output = field{
196+
Name: name(node.Returns.Type),
197+
Type: name(node.Returns.Type),
198+
// TODO: Optional:
199+
}
200+
}
201+
}
202+
203+
types[v.Name] = v
204+
case *qapischema.Event:
205+
v := event{
206+
Name: name(node.Name),
207+
}
208+
209+
if node.Data.Selector.Embed != nil {
210+
v.Data = fieldsOrRef{
211+
Ref: name(node.Data.Selector.Ref),
212+
}
213+
if v.Data.Ref != "" {
214+
v.BoxedData = true
215+
}
216+
217+
for _, member := range node.Data.Selector.Embed.Members {
218+
f := field{
219+
Name: name(member.Name),
220+
Optional: member.Optional,
221+
}
222+
223+
if member.Type.TypeArray != "" {
224+
f.List = true
225+
f.Type = name(member.Type.TypeArray)
226+
} else {
227+
f.Type = name(member.Type.Type)
228+
}
229+
230+
v.Data.AnonFields = append(v.Data.AnonFields, f)
231+
}
232+
}
233+
234+
types[v.Name] = v
235+
case *qapischema.Alternate:
236+
v := alternate{
237+
Name: name(node.Name),
238+
Options: make(map[name]name),
239+
}
240+
241+
for _, opt := range node.Data {
242+
n := opt.Type.Type
243+
if opt.Type.TypeArray != "" {
244+
n = string(opt.Type.TypeArray)
245+
}
246+
v.Options[name(opt.Name)] = name(n)
247+
}
248+
249+
types[v.Name] = v
250+
case *qapischema.Union:
251+
if node.Discriminator == "" {
252+
v := simpleUnion{
253+
Name: name(node.Name),
254+
Options: make(map[name]name),
255+
}
256+
257+
for _, branch := range node.Branches {
258+
b := string(branch.Type.Type)
259+
if branch.Type.TypeArray != "" {
260+
b = string(branch.Type.TypeArray)
261+
}
262+
v.Options[name(branch.Name)] = name(b)
263+
}
264+
265+
types[v.Name] = v
266+
} else {
267+
v := flatUnion{
268+
Name: name(node.Name),
269+
Discriminator: name(node.Discriminator),
270+
Base: fieldsOrRef{
271+
Ref: name(node.Base.Name),
272+
},
273+
Options: make(map[name]name),
274+
}
275+
276+
for _, member := range node.Base.Members {
277+
f := field{
278+
Name: name(member.Name),
279+
Optional: member.Optional,
280+
}
281+
282+
if member.Type.TypeArray != "" {
283+
f.List = true
284+
f.Type = name(member.Type.TypeArray)
285+
} else {
286+
f.Type = name(member.Type.Type)
287+
}
288+
289+
v.Base.AnonFields = append(v.Base.AnonFields, f)
290+
}
291+
292+
for _, branch := range node.Branches {
293+
b := string(branch.Type.Type)
294+
if branch.Type.TypeArray != "" {
295+
b = string(branch.Type.TypeArray)
296+
}
297+
v.Options[name(branch.Name)] = name(b)
298+
}
299+
300+
types[v.Name] = v
301+
}
302+
303+
case *qapischema.Enum:
304+
v := enum{
305+
Name: name(node.Name),
306+
}
307+
308+
for _, val := range node.Values {
309+
v.Values = append(v.Values, name(val.Value))
310+
}
311+
312+
types[v.Name] = v
313+
}
314+
}
315+
316+
return types
317+
}

internal/qmp-gen/parse_test.go

Lines changed: 11 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
"net/http/httptest"
2222
"strings"
2323
"testing"
24+
25+
qapischema "github.com/digitalocean/go-qemu/qapi-schema"
2426
)
2527

2628
func TestGenerate(t *testing.T) {
@@ -202,79 +204,29 @@ func indent(s string) string {
202204
return strings.Replace(s, "\n", "\n ", -1)
203205
}
204206

205-
func TestPyToJSON(t *testing.T) {
206-
tests := []struct {
207-
name, in, out string
208-
}{
209-
{
210-
name: "Basic translation",
211-
in: `{'foo': 42, "bar": 'baz'}`,
212-
out: `{"foo": 42, "bar": "baz"}`,
213-
},
214-
{
215-
name: "Idempotency",
216-
in: `{"foo": 42, "bar": "baz"}`,
217-
out: `{"foo": 42, "bar": "baz"}`,
218-
},
219-
{
220-
name: "Lone comment",
221-
in: `# foo`,
222-
out: ``,
223-
},
224-
{
225-
name: "Whole line comment",
226-
in: `# This is a test
227-
{'foo': 42, 'bar': 'baz'}
228-
# This is another`,
229-
out: `{"foo": 42, "bar": "baz"}`,
230-
},
231-
{
232-
name: "Inline comment",
233-
in: `{'foo': 42, # This is a test
234-
'bar': 'baz'} # This is another`,
235-
// there's a trailing space on the next line.
236-
out: `{"foo": 42,
237-
"bar": "baz"}`,
238-
},
239-
{
240-
name: "Comment in a string",
241-
in: `{'foo': 'look, # a comment!'}`,
242-
out: `{"foo": "look, # a comment!"}`,
243-
},
244-
}
245-
246-
for _, test := range tests {
247-
t.Run(test.name, func(t *testing.T) {
248-
actual := strings.TrimSpace(string(pyToJSON([]byte(test.in))))
249-
if actual != test.out {
250-
t.Errorf(`Wrong output
251-
Input:
252-
%s
253-
Want:
254-
%s
255-
Got:
256-
%s`, indent(test.in), indent(test.out), indent(actual))
257-
}
258-
})
259-
}
260-
}
261-
262207
func testGenerate(t *testing.T, in []byte, checkNeededTypes bool) []byte {
263208
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
264209
_, _ = w.Write(in)
265210
}))
266211
defer s.Close()
267212

268-
defs, err := readDefinitions(s.URL)
213+
data, err := getQAPI(s.URL)
214+
if err != nil {
215+
t.Fatalf("unexpected error: %v", err)
216+
}
217+
218+
tree, err := qapischema.Parse(string(data))
269219
if err != nil {
270220
t.Fatalf("unexpected error: %v", err)
271221
}
272222

273-
symbols, err := parse(defs)
223+
tree, err = completeParseTree(tree, s.URL)
274224
if err != nil {
275225
t.Fatalf("unexpected error: %v", err)
276226
}
277227

228+
symbols := lowerParseTree(tree)
229+
278230
need := symbols
279231
if checkNeededTypes {
280232
need = neededTypes(symbols)

0 commit comments

Comments
 (0)