diff options
Diffstat (limited to 'nexgb/xgbgen')
-rw-r--r-- | nexgb/xgbgen/context.go | 89 | ||||
-rw-r--r-- | nexgb/xgbgen/go.go | 255 | ||||
-rw-r--r-- | nexgb/xgbgen/main.go | 64 | ||||
-rw-r--r-- | nexgb/xgbgen/misc.go | 44 | ||||
-rwxr-xr-x | nexgb/xgbgen/xgbgen | bin | 0 -> 2318165 bytes | |||
-rw-r--r-- | nexgb/xgbgen/xml.go | 298 | ||||
-rw-r--r-- | nexgb/xgbgen/xml_expression.go | 160 | ||||
-rw-r--r-- | nexgb/xgbgen/xml_fields.go | 147 |
8 files changed, 1057 insertions, 0 deletions
diff --git a/nexgb/xgbgen/context.go b/nexgb/xgbgen/context.go new file mode 100644 index 0000000..e5acb12 --- /dev/null +++ b/nexgb/xgbgen/context.go @@ -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)) + // } + // } +} diff --git a/nexgb/xgbgen/go.go b/nexgb/xgbgen/go.go new file mode 100644 index 0000000..eb3f0fb --- /dev/null +++ b/nexgb/xgbgen/go.go @@ -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) + } +} + diff --git a/nexgb/xgbgen/main.go b/nexgb/xgbgen/main.go new file mode 100644 index 0000000..69579a4 --- /dev/null +++ b/nexgb/xgbgen/main.go @@ -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) + } + } +} + diff --git a/nexgb/xgbgen/misc.go b/nexgb/xgbgen/misc.go new file mode 100644 index 0000000..9adcf5d --- /dev/null +++ b/nexgb/xgbgen/misc.go @@ -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, "") +} diff --git a/nexgb/xgbgen/xgbgen b/nexgb/xgbgen/xgbgen Binary files differnew file mode 100755 index 0000000..ef33abc --- /dev/null +++ b/nexgb/xgbgen/xgbgen diff --git a/nexgb/xgbgen/xml.go b/nexgb/xgbgen/xml.go new file mode 100644 index 0000000..0f632b4 --- /dev/null +++ b/nexgb/xgbgen/xml.go @@ -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"` +} + diff --git a/nexgb/xgbgen/xml_expression.go b/nexgb/xgbgen/xml_expression.go new file mode 100644 index 0000000..dd32512 --- /dev/null +++ b/nexgb/xgbgen/xml_expression.go @@ -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 "&": + return wrap(operand1.Eval() & operand2.Eval()) + case "<<": + 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") +} diff --git a/nexgb/xgbgen/xml_fields.go b/nexgb/xgbgen/xml_fields.go new file mode 100644 index 0000000..18be6e3 --- /dev/null +++ b/nexgb/xgbgen/xml_fields.go @@ -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 +} |