Skip to content

Commit bec2f50

Browse files
committed
import new QAPI parser package
1 parent 4ec0473 commit bec2f50

File tree

13 files changed

+2306
-8
lines changed

13 files changed

+2306
-8
lines changed

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
module github.com/digitalocean/go-qemu
22

3-
go 1.15
3+
go 1.18
44

55
require (
6+
github.com/alecthomas/repr v0.1.1
67
github.com/digitalocean/go-libvirt v0.0.0-20220804181439-8648fbde413e
78
github.com/fatih/camelcase v1.0.0
89
golang.org/x/sys v0.0.0-20210510120138-977fb7262007

go.sum

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1+
github.com/alecthomas/repr v0.1.1 h1:87P60cSmareLAxMc4Hro0r2RBY4ROm0dYwkJNpS4pPs=
2+
github.com/alecthomas/repr v0.1.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
13
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
24
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3-
github.com/digitalocean/go-libvirt v0.0.0-20220704152101-6d260d9c16a3 h1:Xo40yaf991bC6M6cPWGmmoWyP67JxtKd9PpBv01rivY=
4-
github.com/digitalocean/go-libvirt v0.0.0-20220704152101-6d260d9c16a3/go.mod h1:o129ljs6alsIQTc8d6eweihqpmmrbxZ2g1jhgjhPykI=
5-
github.com/digitalocean/go-libvirt v0.0.0-20220802191312-ca9ffa5e873e h1:n2CI8AXLnveCM2KuI10OKN4GRyEdEm4217vYueKpTmo=
6-
github.com/digitalocean/go-libvirt v0.0.0-20220802191312-ca9ffa5e873e/go.mod h1:o129ljs6alsIQTc8d6eweihqpmmrbxZ2g1jhgjhPykI=
7-
github.com/digitalocean/go-libvirt v0.0.0-20220804180232-c811be39ab76 h1:HawPl7MaY81IvYgjeR3ND/9an0zCvnGf81sglNb3ePc=
8-
github.com/digitalocean/go-libvirt v0.0.0-20220804180232-c811be39ab76/go.mod h1:o129ljs6alsIQTc8d6eweihqpmmrbxZ2g1jhgjhPykI=
95
github.com/digitalocean/go-libvirt v0.0.0-20220804181439-8648fbde413e h1:SCnqm8SjSa0QqRxXbo5YY//S+OryeJioe17nK+iDZpg=
106
github.com/digitalocean/go-libvirt v0.0.0-20220804181439-8648fbde413e/go.mod h1:o129ljs6alsIQTc8d6eweihqpmmrbxZ2g1jhgjhPykI=
117
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
@@ -39,7 +35,6 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
3935
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
4036
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
4137
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
42-
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
4338
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
4439
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
4540
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

qapi-schema/README.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# qapi-schema
2+
3+
Package qapi-schema is a fully compliant[^1] QAPI[^2] schema language parser.
4+
The QAPI schema language looks approximately like JSON, but it differs
5+
slightly in many ways, which can confuse a regular JSON parser.
6+
7+
## Usage
8+
9+
If you want to parse QAPI schema from your Go code, all you have to do
10+
is called `qapischema.Parse`:
11+
12+
```go
13+
input := `{ 'struct': 'DiskThing',
14+
'data': {
15+
'diskname': {
16+
'type':'str',
17+
'if':'DISKS_HAVE_NAMES' } } }`
18+
schema, _ := qapischema.Parse(input)
19+
```
20+
21+
The above code snippet would produce a `*qapischema.Tree` that looks like this:
22+
23+
```txt
24+
&qapischema.Tree{
25+
Node: qapischema.Root{ },
26+
Children: []*qapischema.Tree{
27+
{
28+
Node: &qapischema.Struct{
29+
Name: "DiskThing",
30+
Members: []qapischema.Member{
31+
{
32+
Name: "diskname",
33+
Type: qapischema.TypeRef{
34+
Type: "str",
35+
},
36+
If: &qapischema.Cond{
37+
If: &qapischema.CondIf("DISKS_HAVE_NAMES"),
38+
},
39+
},
40+
},
41+
},
42+
},
43+
},
44+
}
45+
```
46+
47+
Once the QAPI has been parsed, you can walk the `*qapischema.Tree` and do
48+
whatever it is that you need to do.
49+
50+
The `Node` field in `*qapischema.Tree` is an interface type, and so a type
51+
assertion is required to identify and access the QAPI-type-specific data
52+
in the node you're visiting within the tree.
53+
54+
```go
55+
func visit(tree *qapischema.Tree) {
56+
switch data := tree.Node.(type) {
57+
// Root node, no data, traverse the subtrees in the .Children field.
58+
case qapischema.Root:
59+
case qapischema.Include:
60+
case qapischema.Pragma:
61+
case *qapischema.Struct:
62+
case *qapischema.Union:
63+
case *qapischema.Event:
64+
case *qapischema.Command:
65+
case *qapischema.Alternate:
66+
}
67+
68+
// Process the rest of the document
69+
for _, t := range tree.Children {
70+
visit(t)
71+
}
72+
}
73+
74+
func main() {
75+
tree, _ := qapischema.Parse(input)
76+
77+
visit(tree)
78+
}
79+
```
80+
81+
## Reporting defects
82+
83+
There is a lot of room for improvement with how this parser emits diagnostic
84+
information. That is, it doesn't emit any at all. The parser will simply stop
85+
parsing when it's not able to parse something. It won't complain, it will just
86+
stop.
87+
88+
So, when it comes to identifying which part of the document the parser did not
89+
understand, just compare the input schema to the output until you find the
90+
first element in the input schema that is missing from the parse tree.
91+
92+
There are two utilities included in this module: `qapilex` and `qapiparse`.
93+
94+
`qapiparse` parses QAPI from its stdin and prints a pretty string representation
95+
of the parse tree to stdout. This can be very helpful for figuring out where the
96+
parser stopped.
97+
98+
In your bug report, please include the QAPI input that surfaced the failure to
99+
parse. If possible, try to reduce the QAPI input down to a minimal viable
100+
reproducer.
101+
102+
## Acknowledgements
103+
104+
Many thanks to:
105+
106+
* Thorsten Ball, the author of _Writing an Interpreter in Go_[^3]. The lessons
107+
in that book's chapter on lexing were directly applied to create package
108+
`internal/lex`.
109+
* Jeremy Brown, whose GopherCon 2022 talk[^4] demonstrated simple and elegant
110+
ways to write parser combinators in Go, which directly inspired much of
111+
package `internal/parse`.
112+
113+
[^1]: At least, it's intended to be fully compliant. If it is not, please
114+
file a bug.
115+
116+
[^2]: https://qemu.readthedocs.io/en/latest/devel/qapi-code-gen.html#introduction
117+
118+
[^3]: https://interpreterbook.com/
119+
120+
[^4]: [GopherCon 2022: Jeremy Brown - Parsing w/o Code Generators: Parser Combinators in Go with Generics](
121+
https://www.youtube.com/watch?v=x5p_SJNRB4U)

qapi-schema/applicator.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package qapischema
2+
3+
type alternateField func(a *Alternate)
4+
type alternativeField func(a *Alternative)
5+
type featureField func(f *Feature)
6+
type branchField func(b *Branch)
7+
type commandField func(c *Command)
8+
type enumField func(e *Enum)
9+
type enumValueField func(e *EnumValue)
10+
type eventField func(e *Event)
11+
type memberField func(m *Member)
12+
type pragmaField func(p *Pragma)
13+
type structField func(s *Struct)
14+
type unionField func(u *Union)
15+
16+
type eventDataBoxedTrue struct{}

qapi-schema/cmd/qapilex/main.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
8+
"github.com/alecthomas/repr"
9+
10+
"github.com/digitalocean/go-qemu/qapi-schema/internal/lex"
11+
)
12+
13+
func main() {
14+
if err := run(); err != nil {
15+
fmt.Fprintf(os.Stderr, "%v\n", err)
16+
}
17+
}
18+
19+
func run() error {
20+
input, err := io.ReadAll(os.Stdin)
21+
if err != nil {
22+
return err
23+
}
24+
25+
tokens := lex.Lex(string(input))
26+
repr.Println(tokens)
27+
28+
return nil
29+
}

qapi-schema/cmd/qapiparse/main.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
8+
"github.com/alecthomas/repr"
9+
10+
qapischema "github.com/digitalocean/go-qemu/qapi-schema"
11+
)
12+
13+
func main() {
14+
if err := run(); err != nil {
15+
fmt.Fprintf(os.Stderr, "parse error: %v\n", err)
16+
}
17+
}
18+
19+
func run() error {
20+
input, err := io.ReadAll(os.Stdin)
21+
if err != nil {
22+
return err
23+
}
24+
25+
schema, err := qapischema.Parse(string(input))
26+
if err != nil {
27+
return err
28+
}
29+
30+
repr.Println(schema)
31+
return nil
32+
}

0 commit comments

Comments
 (0)