initial commit. not currently in a working state.

This commit is contained in:
Andrew Gallant (Ocelot)
2012-04-28 23:25:57 -04:00
commit 52a21b415a
16 changed files with 1906 additions and 0 deletions

89
nexgb/xgbgen/context.go Normal file
View File

@@ -0,0 +1,89 @@
package main
import (
"bytes"
"encoding/xml"
"fmt"
"log"
"strings"
)
type Context struct {
xml *XML
out *bytes.Buffer
}
func newContext() *Context {
return &Context{
xml: &XML{},
out: bytes.NewBuffer([]byte{}),
}
}
// Putln calls put and adds a new line to the end of 'format'.
func (c *Context) Putln(format string, v ...interface{}) {
c.Put(format + "\n", v...)
}
// Put is a short alias to write to 'out'.
func (c *Context) Put(format string, v ...interface{}) {
_, err := c.out.WriteString(fmt.Sprintf(format, v...))
if err != nil {
log.Fatalf("There was an error writing to context buffer: %s", err)
}
}
// TypePrefix searches the parsed XML for a type matching 'needle'.
// It then returns the appropriate prefix to be used in source code.
// Note that the core X protocol *is* a namespace, but does not have a prefix.
// Also note that you should probably check the BaseTypeMap and TypeMap
// before calling this function.
func (c *Context) TypePrefix(needle Type) string {
// If this is xproto, quit. No prefixes needed.
if c.xml.Header == "xproto" {
return ""
}
// First check for the type in the current namespace.
if c.xml.HasType(needle) {
return strings.Title(c.xml.Header)
}
// Now check each of the imports...
for _, imp := range c.xml.Imports {
if imp.xml.Header != "xproto" && imp.xml.HasType(needle) {
return strings.Title(imp.xml.Header)
}
}
return ""
}
// Translate is the big daddy of them all. It takes in an XML byte slice
// and writes Go code to the 'out' buffer.
func (c *Context) Translate(xmlBytes []byte) {
err := xml.Unmarshal(xmlBytes, c.xml)
if err != nil {
log.Fatal(err)
}
// Parse all imports
c.xml.Imports.Eval()
// Make sure all top level enumerations have expressions
// (For when there are empty items.)
c.xml.Enums.Eval()
// It's Morphin' Time!
c.xml.Morph(c)
// for _, req := range c.xml.Requests {
// if req.Name != "CreateContext" && req.Name != "MakeCurrent" {
// continue
// }
// log.Println(req.Name)
// for _, field := range req.Fields {
// log.Println("\t", field.XMLName.Local, field.Type.Morph(c))
// }
// }
}

255
nexgb/xgbgen/go.go Normal file
View File

@@ -0,0 +1,255 @@
package main
/*
To the best of my ability, these are all of the Go specific formatting
functions. If I've designed xgbgen correctly, this should be only the
place that you change things to generate code for a new language.
This file is organized as follows:
* Imports and helper variables.
* Manual type and name override maps.
* Helper morphing functions.
* Morphing functions for each "unit".
* Morphing functions for collections of "units".
*/
import (
"strings"
)
/******************************************************************************/
// Manual type and name overrides.
/******************************************************************************/
// BaseTypeMap is a map from X base types to Go types.
// X base types should correspond to the smallest set of X types
// that can be used to rewrite ALL X types in terms of Go types.
// That is, if you remove any of the following types, at least one
// XML protocol description will produce an invalid Go program.
// The types on the left *never* show themselves in the source.
var BaseTypeMap = map[string]string{
"CARD8": "byte",
"CARD16": "uint16",
"CARD32": "uint32",
"INT8": "int8",
"INT16": "int16",
"INT32": "int32",
"BYTE": "byte",
"BOOL": "bool",
"float": "float64",
"double": "float64",
}
// TypeMap is a map from types in the XML to type names that is used
// in the functions that follow. Basically, every occurrence of the key
// type is replaced with the value type.
var TypeMap = map[string]string{
"VISUALTYPE": "VisualInfo",
"DEPTH": "DepthInfo",
"SCREEN": "ScreenInfo",
"Setup": "SetupInfo",
}
// NameMap is the same as TypeMap, but for names.
var NameMap = map[string]string{ }
/******************************************************************************/
// Helper functions that aide in morphing repetive constructs.
// i.e., "structure contents", expressions, type and identifier names, etc.
/******************************************************************************/
// Morph changes every TYPE (not names) into something suitable
// for your language.
func (typ Type) Morph(c *Context) string {
t := string(typ)
// If this is a base type, then write the raw Go type.
if newt, ok := BaseTypeMap[t]; ok {
return newt
}
// If it's in the type map, use that translation.
if newt, ok := TypeMap[t]; ok {
return newt
}
// If it's a resource type, just use 'Id'.
if c.xml.IsResource(typ) {
return "Id"
}
// If there's a namespace to this type, just use it and be done.
if colon := strings.Index(t, ":"); colon > -1 {
namespace := t[:colon]
rest := t[colon+1:]
return splitAndTitle(namespace) + splitAndTitle(rest)
}
// Since there is no namespace, we need to look for a namespace
// in the current context.
return c.TypePrefix(typ) + splitAndTitle(t)
}
// Morph changes every identifier (NOT type) into something suitable
// for your language.
func (name Name) Morph(c *Context) string {
n := string(name)
// If it's in the name map, use that translation.
if newn, ok := NameMap[n]; ok {
return newn
}
return splitAndTitle(n)
}
/******************************************************************************/
// Per element morphing.
// Below are functions that morph a single unit.
/******************************************************************************/
// Import morphing.
func (imp *Import) Morph(c *Context) {
c.Putln("// import \"%s\"", imp.Name)
}
// Enum morphing.
func (enum *Enum) Morph(c *Context) {
c.Putln("const (")
for _, item := range enum.Items {
c.Putln("%s%s = %d", enum.Name.Morph(c), item.Name.Morph(c),
item.Expr.Eval())
}
c.Putln(")\n")
}
// Xid morphing.
func (xid *Xid) Morph(c *Context) {
// Don't emit anything for xid types for now.
// We're going to force them all to simply be 'Id'
// to avoid excessive type converting.
// c.Putln("type %s Id", xid.Name.Morph(c))
}
// TypeDef morphing.
func (typedef *TypeDef) Morph(c *Context) {
c.Putln("type %s %s", typedef.Old.Morph(c), typedef.New.Morph(c))
}
// Struct morphing.
func (strct *Struct) Morph(c *Context) {
}
// Union morphing.
func (union *Union) Morph(c *Context) {
}
// Request morphing.
func (request *Request) Morph(c *Context) {
}
// Event morphing.
func (ev *Event) Morph(c *Context) {
}
// EventCopy morphing.
func (evcopy *EventCopy) Morph(c *Context) {
}
// Error morphing.
func (err *Error) Morph(c *Context) {
}
// ErrorCopy morphing.
func (errcopy *ErrorCopy) Morph(c *Context) {
}
/******************************************************************************/
// Collection morphing.
// Below are functions that morph a collections of units.
// Most of these can probably remain unchanged, but they are useful if you
// need to group all of some "unit" in a single block or something.
/******************************************************************************/
func (imports Imports) Morph(c *Context) {
if len(imports) == 0 {
return
}
c.Putln("// Imports are not required for XGB since everything is in")
c.Putln("// a single package. Still these may be useful for ")
c.Putln("// reference purposes.")
for _, imp := range imports {
imp.Morph(c)
}
}
func (enums Enums) Morph(c *Context) {
c.Putln("// Enums\n")
for _, enum := range enums {
enum.Morph(c)
}
}
func (xids Xids) Morph(c *Context) {
c.Putln("// Xids\n")
for _, xid := range xids {
xid.Morph(c)
}
}
func (typedefs TypeDefs) Morph(c *Context) {
c.Putln("// TypeDefs\n")
for _, typedef := range typedefs {
typedef.Morph(c)
}
}
func (strct Structs) Morph(c *Context) {
c.Putln("// Structs\n")
for _, typedef := range strct {
typedef.Morph(c)
}
}
func (union Unions) Morph(c *Context) {
c.Putln("// Unions\n")
for _, typedef := range union {
typedef.Morph(c)
}
}
func (request Requests) Morph(c *Context) {
c.Putln("// Requests\n")
for _, typedef := range request {
typedef.Morph(c)
}
}
func (event Events) Morph(c *Context) {
c.Putln("// Events\n")
for _, typedef := range event {
typedef.Morph(c)
}
}
func (evcopy EventCopies) Morph(c *Context) {
c.Putln("// Event Copies\n")
for _, typedef := range evcopy {
typedef.Morph(c)
}
}
func (err Errors) Morph(c *Context) {
c.Putln("// Errors\n")
for _, typedef := range err {
typedef.Morph(c)
}
}
func (errcopy ErrorCopies) Morph(c *Context) {
c.Putln("// Error copies\n")
for _, typedef := range errcopy {
typedef.Morph(c)
}
}

64
nexgb/xgbgen/main.go Normal file
View File

@@ -0,0 +1,64 @@
package main
import (
"flag"
"io/ioutil"
"log"
"os"
"os/exec"
"strings"
)
var (
protoPath = flag.String("proto-path",
"/usr/share/xcb", "path to directory of X protocol XML files")
gofmt = flag.Bool("gofmt", true,
"When disabled, gofmt will not be run before outputting Go code")
)
func usage() {
basename := os.Args[0]
if lastSlash := strings.LastIndex(basename, "/"); lastSlash > -1 {
basename = basename[lastSlash+1:]
}
log.Printf("Usage: %s [flags] xml-file", basename)
flag.PrintDefaults()
os.Exit(1)
}
func init() {
log.SetFlags(0)
}
func main() {
flag.Usage = usage
flag.Parse()
if flag.NArg() != 1 {
log.Printf("A single XML protocol file can be processed at once.")
flag.Usage()
}
// Read the single XML file into []byte
xmlBytes, err := ioutil.ReadFile(flag.Arg(0))
if err != nil {
log.Fatal(err)
}
// Initialize the buffer, parse it, and filter it through gofmt.
c := newContext()
c.Translate(xmlBytes)
if !*gofmt {
c.out.WriteTo(os.Stdout)
} else {
cmdGofmt := exec.Command("gofmt")
cmdGofmt.Stdin = c.out
cmdGofmt.Stdout = os.Stdout
err = cmdGofmt.Run()
if err != nil {
log.Fatal(err)
}
}
}

44
nexgb/xgbgen/misc.go Normal file
View File

@@ -0,0 +1,44 @@
package main
import (
"regexp"
"strings"
)
// AllCaps is a regex to test if a string identifier is made of
// all upper case letters.
var AllCaps = regexp.MustCompile("^[A-Z0-9]+$")
// popCount counts number of bits 'set' in mask.
func popCount(mask uint) uint {
m := uint32(mask)
n := uint(0)
for i := uint32(0); i < 32; i++ {
if m&(1<<i) != 0 {
n++
}
}
return n
}
// splitAndTitle takes a string, splits it by underscores, capitalizes the
// first letter of each chunk, and smushes'em back together.
func splitAndTitle(s string) string {
// If the string is all caps, lower it and capitalize first letter.
if AllCaps.MatchString(s) {
return strings.Title(strings.ToLower(s))
}
// If the string has no underscores, leave it be.
if i := strings.Index(s, "_"); i == -1 {
return s
}
// Now split the name at underscores, capitalize the first
// letter of each chunk, and smush'em back together.
chunks := strings.Split(s, "_")
for i, chunk := range chunks {
chunks[i] = strings.Title(strings.ToLower(chunk))
}
return strings.Join(chunks, "")
}

BIN
nexgb/xgbgen/xgbgen Executable file

Binary file not shown.

298
nexgb/xgbgen/xml.go Normal file
View File

@@ -0,0 +1,298 @@
package main
import (
"encoding/xml"
"io/ioutil"
"log"
"time"
)
type XML struct {
// Root 'xcb' element properties.
XMLName xml.Name `xml:"xcb"`
Header string `xml:"header,attr"`
ExtensionXName string `xml:"extension-xname,attr"`
ExtensionName string `xml:"extension-name,attr"`
MajorVersion string `xml:"major-version,attr"`
MinorVersion string `xml:"minor-version,attr"`
// Types for all top-level elements.
// First are the simple ones.
Imports Imports `xml:"import"`
Enums Enums `xml:"enum"`
Xids Xids `xml:"xidtype"`
XidUnions Xids `xml:"xidunion"`
TypeDefs TypeDefs `xml:"typedef"`
EventCopies EventCopies `xml:"eventcopy"`
ErrorCopies ErrorCopies `xml:"errorcopy"`
// Here are the complex ones, i.e., anything with "structure contents"
Structs Structs `xml:"struct"`
Unions Unions `xml:"union"`
Requests Requests `xml:"request"`
Events Events `xml:"event"`
Errors Errors `xml:"error"`
}
// Morph cascades down all of the XML and calls each type's corresponding
// Morph function with itself as an argument (the context).
func (x *XML) Morph(c *Context) {
// Start the header...
c.Putln("package xgb")
c.Putln("/*")
c.Putln("\tX protocol API for '%s.xml'.", c.xml.Header)
c.Putln("\tThis file is automatically generated. Edit at your own peril!")
c.Putln("\tGenerated on %s",
time.Now().Format("Jan 2, 2006 at 3:04:05pm MST"))
c.Putln("*/")
c.Putln("")
x.Imports.Morph(c)
c.Putln("")
x.Enums.Morph(c)
c.Putln("")
x.Xids.Morph(c)
c.Putln("")
x.XidUnions.Morph(c)
c.Putln("")
x.TypeDefs.Morph(c)
c.Putln("")
x.Structs.Morph(c)
c.Putln("")
x.Unions.Morph(c)
c.Putln("")
x.Requests.Morph(c)
c.Putln("")
x.Events.Morph(c)
c.Putln("")
x.Errors.Morph(c)
c.Putln("")
x.EventCopies.Morph(c)
c.Putln("")
x.ErrorCopies.Morph(c)
c.Putln("")
}
// IsResource returns true if the 'needle' type is a resource type.
// i.e., an "xid"
func (x *XML) IsResource(needle Type) bool {
for _, xid := range x.Xids {
if needle == xid.Name {
return true
}
}
for _, xidunion := range x.XidUnions {
if needle == xidunion.Name {
return true
}
}
for _, imp := range x.Imports {
if imp.xml.IsResource(needle) {
return true
}
}
return false
}
// HasType returns true if the 'needle' type can be found in the protocol
// description represented by 'x'.
func (x *XML) HasType(needle Type) bool {
for _, enum := range x.Enums {
if needle == enum.Name {
return true
}
}
for _, xid := range x.Xids {
if needle == xid.Name {
return true
}
}
for _, xidunion := range x.XidUnions {
if needle == xidunion.Name {
return true
}
}
for _, typedef := range x.TypeDefs {
if needle == typedef.New {
return true
}
}
for _, evcopy := range x.EventCopies {
if needle == evcopy.Name {
return true
}
}
for _, errcopy := range x.ErrorCopies {
if needle == errcopy.Name {
return true
}
}
for _, strct := range x.Structs {
if needle == strct.Name {
return true
}
}
for _, union := range x.Unions {
if needle == union.Name {
return true
}
}
for _, ev := range x.Events {
if needle == ev.Name {
return true
}
}
for _, err := range x.Errors {
if needle == err.Name {
return true
}
}
return false
}
type Name string
type Type string
type Imports []*Import
func (imports Imports) Eval() {
for _, imp := range imports {
xmlBytes, err := ioutil.ReadFile(*protoPath + "/" + imp.Name + ".xml")
if err != nil {
log.Fatalf("Could not read X protocol description for import " +
"'%s' because: %s", imp.Name, err)
}
imp.xml = &XML{}
err = xml.Unmarshal(xmlBytes, imp.xml)
if err != nil {
log.Fatal("Could not parse X protocol description for import " +
"'%s' because: %s", imp.Name, err)
}
}
}
type Import struct {
Name string `xml:",chardata"`
xml *XML `xml:"-"`
}
type Enums []Enum
// Eval on the list of all enum types goes through and forces every enum
// item to have a valid expression.
// This is necessary because when an item is empty, it is defined to have
// the value of "one more than that of the previous item, or 0 for the first
// item".
func (enums Enums) Eval() {
for _, enum := range enums {
nextValue := uint(0)
for _, item := range enum.Items {
if item.Expr == nil {
item.Expr = newValueExpression(nextValue)
nextValue++
} else {
nextValue = item.Expr.Eval() + 1
}
}
}
}
type Enum struct {
Name Type `xml:"name,attr"`
Items []*EnumItem `xml:"item"`
}
type EnumItem struct {
Name Name `xml:"name,attr"`
Expr *Expression `xml:",any"`
}
type Xids []*Xid
type Xid struct {
XMLName xml.Name
Name Type `xml:"name,attr"`
}
type TypeDefs []*TypeDef
type TypeDef struct {
Old Type `xml:"oldname,attr"`
New Type `xml:"newname,attr"`
}
type EventCopies []*EventCopy
type EventCopy struct {
Name Type `xml:"name,attr"`
Number string `xml:"number,attr"`
Ref Type `xml:"ref,attr"`
}
type ErrorCopies []*ErrorCopy
type ErrorCopy struct {
Name Type `xml:"name,attr"`
Number string `xml:"number,attr"`
Ref Type `xml:"ref,attr"`
}
type Structs []*Struct
type Struct struct {
Name Type `xml:"name,attr"`
Fields []*Field `xml:",any"`
}
type Unions []*Union
type Union struct {
Name Type `xml:"name,attr"`
Fields []*Field `xml:",any"`
}
type Requests []*Request
type Request struct {
Name Type `xml:"name,attr"`
Opcode int `xml:"opcode,attr"`
Combine bool `xml:"combine-adjacent,attr"`
Fields []*Field `xml:",any"`
Reply *Reply `xml:"reply"`
}
type Reply struct {
Fields []*Field `xml:",any"`
}
type Events []*Event
type Event struct {
Name Type `xml:"name,attr"`
Number int `xml:"number,attr"`
NoSequence bool `xml:"no-sequence-number,true"`
Fields []*Field `xml:",any"`
}
type Errors []*Error
type Error struct {
Name Type `xml:"name,attr"`
Number int `xml:"number,attr"`
Fields []*Field `xml:",any"`
}

View File

@@ -0,0 +1,160 @@
package main
import (
"encoding/xml"
"fmt"
"log"
"strconv"
)
type Expression struct {
XMLName xml.Name
Exprs []*Expression `xml:",any"`
Data string `xml:",chardata"`
Op string `xml:"op,attr"`
Ref string `xml:"ref,attr"`
}
func newValueExpression(v uint) *Expression {
return &Expression{
XMLName: xml.Name{Local: "value"},
Data: fmt.Sprintf("%d", v),
}
}
// String is for debugging. For actual use, please use 'Morph'.
func (e *Expression) String() string {
switch e.XMLName.Local {
case "op":
return fmt.Sprintf("(%s %s %s)", e.Exprs[0], e.Op, e.Exprs[1])
case "unop":
return fmt.Sprintf("(%s (%s))", e.Op, e.Exprs[0])
case "popcount":
return fmt.Sprintf("popcount(%s)", e.Exprs[0])
case "fieldref":
fallthrough
case "value":
return fmt.Sprintf("%s", e.Data)
case "bit":
return fmt.Sprintf("(1 << %s)", e.Data)
case "enumref":
return fmt.Sprintf("%s%s", e.Ref, e.Data)
case "sumof":
return fmt.Sprintf("sum(%s)", e.Ref)
default:
log.Panicf("Unrecognized expression element: %s", e.XMLName.Local)
}
panic("unreachable")
}
// Eval is used to *attempt* to compute a concrete value for a particular
// expression. This is used in the initial setup to instantiate values for
// empty items in enums.
// We can't compute a concrete value for expressions that rely on a context,
// i.e., some field value.
func (e *Expression) Eval() uint {
switch e.XMLName.Local {
case "op":
if len(e.Exprs) != 2 {
log.Panicf("'op' found %d expressions; expected 2.", len(e.Exprs))
}
return e.BinaryOp(e.Exprs[0], e.Exprs[1]).Eval()
case "unop":
if len(e.Exprs) != 1 {
log.Panicf("'unop' found %d expressions; expected 1.", len(e.Exprs))
}
return e.UnaryOp(e.Exprs[0]).Eval()
case "popcount":
if len(e.Exprs) != 1 {
log.Panicf("'popcount' found %d expressions; expected 1.",
len(e.Exprs))
}
return popCount(e.Exprs[0].Eval())
case "value":
val, err := strconv.Atoi(e.Data)
if err != nil {
log.Panicf("Could not convert '%s' in 'value' expression to int.",
e.Data)
}
return uint(val)
case "bit":
bit, err := strconv.Atoi(e.Data)
if err != nil {
log.Panicf("Could not convert '%s' in 'bit' expression to int.",
e.Data)
}
if bit < 0 || bit > 31 {
log.Panicf("A 'bit' literal must be in the range [0, 31], but " +
" is %d", bit)
}
return 1 << uint(bit)
case "fieldref":
log.Panicf("Cannot compute concrete value of 'fieldref' in " +
"expression '%s'.", e)
case "enumref":
log.Panicf("Cannot compute concrete value of 'enumref' in " +
"expression '%s'.", e)
case "sumof":
log.Panicf("Cannot compute concrete value of 'sumof' in " +
"expression '%s'.", e)
}
log.Panicf("Unrecognized tag '%s' in expression context. Expected one of " +
"op, fieldref, value, bit, enumref, unop, sumof or popcount.",
e.XMLName.Local)
panic("unreachable")
}
func (e *Expression) BinaryOp(operand1, operand2 *Expression) *Expression {
if e.XMLName.Local != "op" {
log.Panicf("Cannot perform binary operation on non-op expression: %s",
e.XMLName.Local)
}
if len(e.Op) == 0 {
log.Panicf("Cannot perform binary operation without operator for: %s",
e.XMLName.Local)
}
wrap := newValueExpression
switch e.Op {
case "+":
return wrap(operand1.Eval() + operand2.Eval())
case "-":
return wrap(operand1.Eval() + operand2.Eval())
case "*":
return wrap(operand1.Eval() * operand2.Eval())
case "/":
return wrap(operand1.Eval() / operand2.Eval())
case "&amp;":
return wrap(operand1.Eval() & operand2.Eval())
case "&lt;&lt;":
return wrap(operand1.Eval() << operand2.Eval())
}
log.Panicf("Invalid binary operator '%s' for '%s' expression.",
e.Op, e.XMLName.Local)
panic("unreachable")
}
func (e *Expression) UnaryOp(operand *Expression) *Expression {
if e.XMLName.Local != "unop" {
log.Panicf("Cannot perform unary operation on non-unop expression: %s",
e.XMLName.Local)
}
if len(e.Op) == 0 {
log.Panicf("Cannot perform unary operation without operator for: %s",
e.XMLName.Local)
}
switch e.Op {
case "~":
return newValueExpression(^operand.Eval())
}
log.Panicf("Invalid unary operator '%s' for '%s' expression.",
e.Op, e.XMLName.Local)
panic("unreachable")
}

147
nexgb/xgbgen/xml_fields.go Normal file
View File

@@ -0,0 +1,147 @@
package main
/*
A series of fields should be taken as "structure contents", and *not*
just the single 'field' elements. Namely, 'fields' subsumes 'field'
elements.
More particularly, 'fields' corresponds to list, in order, of any of the
follow elements: pad, field, list, localfield, exprfield, valueparm
and switch.
Thus, the 'Field' type must contain the union of information corresponding
to all aforementioned fields.
This would ideally be a better job for interfaces, but I could not figure
out how to make them jive with Go's XML package. (And I don't really feel
up to type translation.)
*/
import (
"encoding/xml"
"fmt"
"log"
"strings"
)
type Field struct {
XMLName xml.Name
// For 'pad' element
Bytes int `xml:"bytes,attr"`
// For 'field', 'list', 'localfield', 'exprfield' and 'switch' elements.
Name string `xml:"name,attr"`
// For 'field', 'list', 'localfield', and 'exprfield' elements.
Type Type `xml:"type,attr"`
// For 'list', 'exprfield' and 'switch' elements.
Expr *Expression `xml:",any"`
// For 'valueparm' element.
ValueMaskType Type `xml:"value-mask-type,attr"`
ValueMaskName string `xml:"value-mask-name,attr"`
ValueListName string `xml:"value-list-name,attr"`
// For 'switch' element.
Bitcases []*Bitcase `xml:"bitcase"`
// I don't know which elements these are for. The documentation is vague.
// They also seem to be completely optional.
OptEnum Type `xml:"enum,attr"`
OptMask Type `xml:"mask,attr"`
OptAltEnum Type `xml:"altenum,attr"`
}
// String is for debugging purposes.
func (f *Field) String() string {
switch f.XMLName.Local {
case "pad":
return fmt.Sprintf("pad (%d bytes)", f.Bytes)
case "field":
return fmt.Sprintf("field (type = '%s', name = '%s')", f.Type, f.Name)
case "list":
return fmt.Sprintf("list (type = '%s', name = '%s', length = '%s')",
f.Type, f.Name, f.Expr)
case "localfield":
return fmt.Sprintf("localfield (type = '%s', name = '%s')",
f.Type, f.Name)
case "exprfield":
return fmt.Sprintf("exprfield (type = '%s', name = '%s', expr = '%s')",
f.Type, f.Name, f.Expr)
case "valueparam":
return fmt.Sprintf("valueparam (type = '%s', name = '%s', list = '%s')",
f.ValueMaskType, f.ValueMaskName, f.ValueListName)
case "switch":
bitcases := make([]string, len(f.Bitcases))
for i, bitcase := range f.Bitcases {
bitcases[i] = bitcase.StringPrefix("\t")
}
return fmt.Sprintf("switch (name = '%s', expr = '%s')\n\t%s",
f.Name, f.Expr, strings.Join(bitcases, "\n\t"))
default:
log.Panicf("Unrecognized field element: %s", f.XMLName.Local)
}
panic("unreachable")
}
// Bitcase represents a single expression followed by any number of fields.
// Namely, if the switch's expression (all bitcases are inside a switch),
// and'd with the bitcase's expression is equal to the bitcase expression,
// then the fields should be included in its parent structure.
// Note that since a bitcase is unique in that expressions and fields are
// siblings, we must exhaustively search for one of them. Essentially,
// it's the closest thing to a Union I can get to in Go without interfaces.
// Would an '<expression>' tag have been too much to ask? :-(
type Bitcase struct {
Fields []*Field `xml:",any"`
// All the different expressions.
// When it comes time to choose one, use the 'Expr' method.
ExprOp *Expression `xml:"op"`
ExprUnOp *Expression `xml:"unop"`
ExprField *Expression `xml:"fieldref"`
ExprValue *Expression `xml:"value"`
ExprBit *Expression `xml:"bit"`
ExprEnum *Expression `xml:"enumref"`
ExprSum *Expression `xml:"sumof"`
ExprPop *Expression `xml:"popcount"`
}
// StringPrefix is for debugging purposes only.
// StringPrefix takes a string to prefix to every extra line for formatting.
func (b *Bitcase) StringPrefix(prefix string) string {
fields := make([]string, len(b.Fields))
for i, field := range b.Fields {
fields[i] = fmt.Sprintf("%s%s", prefix, field)
}
return fmt.Sprintf("%s\n\t%s%s", b.Expr(), prefix,
strings.Join(fields, "\n\t"))
}
// Expr chooses the only non-nil Expr* field from Bitcase.
// Panic if there is more than one non-nil expression.
func (b *Bitcase) Expr() *Expression {
choices := []*Expression{
b.ExprOp, b.ExprUnOp, b.ExprField, b.ExprValue,
b.ExprBit, b.ExprEnum, b.ExprSum, b.ExprPop,
}
var choice *Expression = nil
numNonNil := 0
for _, c := range choices {
if c != nil {
numNonNil++
choice = c
}
}
if choice == nil {
log.Panicf("No top level expression found in a bitcase.")
}
if numNonNil > 1 {
log.Panicf("More than one top-level expression was found in a bitcase.")
}
return choice
}