From 18b2d420b092d71313f0c05210c3038ff32483e7 Mon Sep 17 00:00:00 2001 From: "Andrew Gallant (Ocelot)" Date: Sun, 6 May 2012 02:21:31 -0400 Subject: [PATCH] added documentation and did some slight restructuring. it's party time. --- nexgb/xgbgen/context.go | 2 + nexgb/xgbgen/doc.go | 74 ++++++++ nexgb/xgbgen/expression.go | 41 +++++ nexgb/xgbgen/field.go | 60 ++++++- nexgb/xgbgen/misc.go | 4 +- nexgb/xgbgen/protocol.go | 41 +++++ .../{representation.go => request_reply.go} | 62 +++---- nexgb/xgbgen/size.go | 9 + nexgb/xgbgen/xml.go | 64 +++---- nexgb/xgbgen/xml_expression.go | 160 ------------------ nexgb/xgbgen/xml_fields.go | 71 +------- 11 files changed, 284 insertions(+), 304 deletions(-) create mode 100644 nexgb/xgbgen/doc.go create mode 100644 nexgb/xgbgen/protocol.go rename nexgb/xgbgen/{representation.go => request_reply.go} (59%) delete mode 100644 nexgb/xgbgen/xml_expression.go diff --git a/nexgb/xgbgen/context.go b/nexgb/xgbgen/context.go index 3e484f3..d433531 100644 --- a/nexgb/xgbgen/context.go +++ b/nexgb/xgbgen/context.go @@ -8,6 +8,8 @@ import ( "time" ) +// Context represents the protocol we're converting to Go, and a writer +// buffer to write the Go source to. type Context struct { protocol *Protocol out *bytes.Buffer diff --git a/nexgb/xgbgen/doc.go b/nexgb/xgbgen/doc.go new file mode 100644 index 0000000..4ba6145 --- /dev/null +++ b/nexgb/xgbgen/doc.go @@ -0,0 +1,74 @@ +/* +xgbgen constructs Go source files from xproto XML description files. xgbgen +accomplishes the same task as the Python code generator for XCB and xpyb. + +Usage: + xgbgen [flags] some-protocol.xml + +The flags are: + --proto-path path + The path to a directory containing xproto XML description files. + This is only necessary when 'some-protocol.xml' imports other + protocol files. + --gofmt=true + When false, the outputted Go code will not be gofmt'd. And it won't + be very pretty at all. This is typically useful if there are syntax + errors that need to be debugged in code generation. gofmt will hiccup; + this will allow you to see the raw code. + +How it works + +xgbgen works by parsing the input XML file using Go's encoding/xml package. +The majority of this work is done in xml.go and xml_fields.go, where the +appropriate types are declared. + +Due to the nature of the XML in the protocol description files, the types +required to parse the XML are not well suited to reasoning about code +generation. Because of this, all data parsed in the XML types is translated +into more reasonable types. This translation is done in translation.go, +and is mainly grunt work. (The only interesting tidbits are the translation +of XML names to Go source names, and connecting fields with their +appropriate types.) + +The organization of these types is greatly +inspired by the description of the XML found here: +http://cgit.freedesktop.org/xcb/proto/tree/doc/xml-xcb.txt + +These types come with a lot of supporting methods to make their use in +code generation easier. They can be found in expression.go, field.go, +protocol.go, request_reply.go and type.go. Of particular interest are +expression evaluation and size calculation (in bytes). + +These types also come with supporting methods that convert their +representation into Go source code. I've quartered such methods in +go.go, go_error.go, go_event.go, go_list.go, go_request_reply.go, +go_single_field.go, go_struct.go and go_union.go. The idea is to keep +as much of the Go specific code generation in one area as possible. Namely, +while not *all* Go related code is found in the 'go*.go' files, *most* +of it is. (If there's any interest in using xgbgen for other languages, +I'd be happy to try and make xgbgen a little more friendly in this regard. +I did, however, design xgbgen with this in mind, so it shouldn't involve +anything as serious as a re-design.) + +Why + +I wrote xgbgen because I found the existing code generator that was written in +Python to be unwieldy. In particular, static and strong typing greatly helped +me reason better about the code generation task. + +What does not work + +The core X protocol should be completely working. As far as I know, most +extensions should work too, although I've only tested (and not much) the +Xinerama and RandR extensions. + +XKB does not work. I don't have any real plans of working on this unless there +is demand and I have some test cases to work with. (i.e., even if I could get +something generated for XKB, I don't have the inclination to understand it +enough to verify that it works.) XKB poses several extremely difficult +problems that XCB also has trouble with. More info on that can be found at +http://cgit.freedesktop.org/xcb/libxcb/tree/doc/xkb_issues and +http://cgit.freedesktop.org/xcb/libxcb/tree/doc/xkb_internals. + +*/ +package main diff --git a/nexgb/xgbgen/expression.go b/nexgb/xgbgen/expression.go index 2350979..721ebfd 100644 --- a/nexgb/xgbgen/expression.go +++ b/nexgb/xgbgen/expression.go @@ -5,11 +5,32 @@ import ( "log" ) +// Expression represents all the different forms of expressions possible in +// side an XML protocol description file. It's also received a few custom +// addendums to make applying special functions (like padding) easier. type Expression interface { + // Concrete determines whether this particular expression can be computed + // to some constant value inside xgbgen. (The alternative is that the + // expression can only be computed with values at run time of the + // generated code.) Concrete() bool + + // Eval evaluates a concrete expression. It is an error to call Eval + // on any expression that is not concrete (or contains any sub-expression + // that is not concrete). Eval() uint + + // Reduce attempts to evaluate any concrete sub-expressions. + // i.e., (1 + 2 * (5 + 1 + someSizeOfStruct) reduces to + // (3 * (6 + someSizeOfStruct)). + // 'prefix' is used preprended to any field reference name. Reduce(prefix string) string + + // String is an alias for Reduce("") String() string + + // Initialize makes sure all names in this expression and any subexpressions + // have been translated to Go source names. Initialize(p *Protocol) } @@ -41,12 +62,17 @@ func (e *Function) Initialize(p *Protocol) { e.Expr.Initialize(p) } +// BinaryOp is an expression that performs some operation (defined in the XML +// file) with Expr1 and Expr2 as operands. type BinaryOp struct { Op string Expr1 Expression Expr2 Expression } +// newBinaryOp constructs a new binary expression when both expr1 and expr2 +// are not nil. If one or both are nil, then the non-nil expression is +// returned unchanged or nil is returned. func newBinaryOp(op string, expr1, expr2 Expression) Expression { switch { case expr1 != nil && expr2 != nil: @@ -124,6 +150,8 @@ func (e *BinaryOp) Initialize(p *Protocol) { e.Expr2.Initialize(p) } +// UnaryOp is the same as BinaryOp, except it's a unary operator with only +// one sub-expression. type UnaryOp struct { Op string Expr Expression @@ -158,6 +186,8 @@ func (e *UnaryOp) Initialize(p *Protocol) { e.Expr.Initialize(p) } +// Padding represents the application of the 'pad' function to some +// sub-expression. type Padding struct { Expr Expression } @@ -185,6 +215,8 @@ func (e *Padding) Initialize(p *Protocol) { e.Expr.Initialize(p) } +// PopCount represents the application of the 'PopCount' function to +// some sub-expression. type PopCount struct { Expr Expression } @@ -212,6 +244,7 @@ func (e *PopCount) Initialize(p *Protocol) { e.Expr.Initialize(p) } +// Value represents some constant integer. type Value struct { v uint } @@ -234,6 +267,7 @@ func (e *Value) String() string { func (e *Value) Initialize(p *Protocol) {} +// Bit represents some bit whose value is computed by '1 << bit'. type Bit struct { b uint } @@ -256,6 +290,8 @@ func (e *Bit) String() string { func (e *Bit) Initialize(p *Protocol) {} +// FieldRef represents a reference to some variable in the generated code +// with name Name. type FieldRef struct { Name string } @@ -285,6 +321,9 @@ func (e *FieldRef) Initialize(p *Protocol) { e.Name = SrcName(p, e.Name) } +// EnumRef represents a reference to some enumeration field. +// EnumKind is the "group" an EnumItem is the name of the specific enumeration +// value inside that group. type EnumRef struct { EnumKind Type EnumItem string @@ -312,6 +351,8 @@ func (e *EnumRef) Initialize(p *Protocol) { e.EnumItem = SrcName(p, e.EnumItem) } +// SumOf represents a summation of the variable in the generated code named by +// Name. It is not currently used. (It's XKB voodoo.) type SumOf struct { Name string } diff --git a/nexgb/xgbgen/field.go b/nexgb/xgbgen/field.go index 5041f77..725f3de 100644 --- a/nexgb/xgbgen/field.go +++ b/nexgb/xgbgen/field.go @@ -6,20 +6,48 @@ import ( "strings" ) +// Field corresponds to any field described in an XML protocol description +// file. This includes struct fields, union fields, request fields, +// reply fields and so on. +// To make code generation easier, fields that have types are also stored. +// Note that not all fields support all methods defined in this interface. +// For instance, a padding field does not have a source name. type Field interface { + // Initialize sets up the source name of this field. Initialize(p *Protocol) + + // SrcName is the Go source name of this field. SrcName() string + + // XmlName is the name of this field from the XML file. XmlName() string + + // SrcType is the Go source type name of this field. SrcType() string + + // Size returns an expression that computes the size (in bytes) + // of this field. Size() Size + // Define writes the Go code to declare this field (in a struct definition). Define(c *Context) + + // Read writes the Go code to convert a byte slice to a Go value + // of this field. + // 'prefix' is the prefix of the name of the Go value. Read(c *Context, prefix string) + + // Write writes the Go code to convert a Go value to a byte slice of + // this field. + // 'prefix' is the prefix of the name of the Go value. Write(c *Context, prefix string) } func (pad *PadField) Initialize(p *Protocol) {} +// PadField represents any type of padding. It is omitted from +// definitions, but is used in Read/Write to increment the buffer index. +// It is also used in size calculation. type PadField struct { Bytes uint } @@ -40,6 +68,8 @@ func (p *PadField) Size() Size { return newFixedSize(p.Bytes) } +// SingleField represents most of the fields in an XML protocol description. +// It corresponds to any single value. type SingleField struct { srcName string xmlName string @@ -67,6 +97,7 @@ func (f *SingleField) Size() Size { return f.Type.Size() } +// ListField represents a list of values. type ListField struct { srcName string xmlName string @@ -89,6 +120,9 @@ func (f *ListField) SrcType() string { return fmt.Sprintf("[]%s", f.Type.SrcName()) } +// Length computes the *number* of values in a list. +// If this ListField does not have any length expression, we throw our hands +// up and simply compute the 'len' of the field name of this list. func (f *ListField) Length() Size { if f.LengthExpr == nil { return newExpressionSize(&Function{ @@ -101,6 +135,12 @@ func (f *ListField) Length() Size { return newExpressionSize(f.LengthExpr) } +// Size computes the *size* of a list (in bytes). +// It it typically a simple matter of multiplying the length of the list by +// the size of the type of the list. +// But if it's a list of struct where the struct has a list field, we use a +// special function written in go_struct.go to compute the size (since the +// size in this case can only be computed recursively). func (f *ListField) Size() Size { simpleLen := &Function{ Name: "pad", @@ -120,11 +160,6 @@ func (f *ListField) Size() Size { } case *Union: return newExpressionSize(simpleLen) - // sizeFun := &Function{ - // Name: fmt.Sprintf("%sListSize", f.Type.SrcName()), - // Expr: &FieldRef{Name: f.SrcName()}, - // } - // return newExpressionSize(sizeFun) case *Base: return newExpressionSize(simpleLen) case *Resource: @@ -145,10 +180,14 @@ func (f *ListField) Initialize(p *Protocol) { } } +// LocalField is exactly the same as a regular SingleField, except it isn't +// sent over the wire. (i.e., it's probably used to compute an ExprField). type LocalField struct { *SingleField } +// ExprField is a field that is not parameterized, but is computed from values +// of other fields. type ExprField struct { srcName string xmlName string @@ -178,6 +217,9 @@ func (f *ExprField) Initialize(p *Protocol) { f.Expr.Initialize(p) } +// ValueField represents two fields in one: a mask and a list of 4-byte +// integers. The mask specifies which kinds of values are in the list. +// (i.e., See ConfigureWindow, CreateWindow, ChangeWindowAttributes, etc.) type ValueField struct { Parent interface{} MaskType Type @@ -197,6 +239,10 @@ func (f *ValueField) SrcType() string { return f.MaskType.SrcName() } +// Size computes the size in bytes of the combination of the mask and list +// in this value field. +// The expression to compute this looks complicated, but it's really just +// the number of bits set in the mask multiplied 4 (and padded of course). func (f *ValueField) Size() Size { maskSize := f.MaskType.Size() listSize := newExpressionSize(&Function{ @@ -234,6 +280,8 @@ func (f *ValueField) Initialize(p *Protocol) { f.ListName = SrcName(p, f.ListName) } +// SwitchField represents a 'switch' element in the XML protocol description +// file. It is not currently used. (i.e., it is XKB voodoo.) type SwitchField struct { Name string Expr Expression @@ -270,6 +318,8 @@ func (f *SwitchField) Initialize(p *Protocol) { } } +// Bitcase represents a single bitcase inside a switch expression. +// It is not currently used. (i.e., it's XKB voodoo.) type Bitcase struct { Fields []Field Expr Expression diff --git a/nexgb/xgbgen/misc.go b/nexgb/xgbgen/misc.go index 13c4cc2..85d788f 100644 --- a/nexgb/xgbgen/misc.go +++ b/nexgb/xgbgen/misc.go @@ -7,7 +7,7 @@ import ( // AllCaps is a regex to test if a string identifier is made of // all upper case letters. -var AllCaps = regexp.MustCompile("^[A-Z0-9]+$") +var allCaps = regexp.MustCompile("^[A-Z0-9]+$") // popCount counts number of bits 'set' in mask. func popCount(mask uint) uint { @@ -30,7 +30,7 @@ func pad(n int) int { // 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) { + if allCaps.MatchString(s) { return strings.Title(strings.ToLower(s)) } diff --git a/nexgb/xgbgen/protocol.go b/nexgb/xgbgen/protocol.go new file mode 100644 index 0000000..505b400 --- /dev/null +++ b/nexgb/xgbgen/protocol.go @@ -0,0 +1,41 @@ +package main + +import ( + "strings" +) + +// Protocol is a type that encapsulates all information about one +// particular XML file. It also contains links to other protocol types +// if this protocol imports other other extensions. The import relationship +// is recursive. +type Protocol struct { + Name string + ExtXName string + ExtName string + MajorVersion string + MinorVersion string + + Imports []*Protocol + Types []Type + Requests []*Request +} + +// Initialize traverses all structures, looks for 'Translation' type, +// and looks up the real type in the namespace. It also sets the source +// name for all relevant fields/structures. +// This is necessary because we don't traverse the XML in order initially. +func (p *Protocol) Initialize() { + for _, typ := range p.Types { + typ.Initialize(p) + } + for _, req := range p.Requests { + req.Initialize(p) + } +} + +// isExt returns true if this protocol is an extension. +// i.e., it's name isn't "xproto". +func (p *Protocol) isExt() bool { + return strings.ToLower(p.Name) == "xproto" +} + diff --git a/nexgb/xgbgen/representation.go b/nexgb/xgbgen/request_reply.go similarity index 59% rename from nexgb/xgbgen/representation.go rename to nexgb/xgbgen/request_reply.go index 62a3eb2..7cd2859 100644 --- a/nexgb/xgbgen/representation.go +++ b/nexgb/xgbgen/request_reply.go @@ -7,40 +7,19 @@ import ( "unicode" ) -type Protocol struct { - Name string - ExtXName string - ExtName string - MajorVersion string - MinorVersion string - - Imports []*Protocol - Types []Type - Requests []*Request -} - -// Initialize traverses all structures, looks for 'Translation' type, -// and looks up the real type in the namespace. It also sets the source -// name for all relevant fields/structures. -// This is necessary because we don't traverse the XML in order initially. -func (p *Protocol) Initialize() { - for _, typ := range p.Types { - typ.Initialize(p) - } - for _, req := range p.Requests { - req.Initialize(p) - } -} - +// Request represents all XML 'request' nodes. +// If the request doesn't have a reply, Reply is nil. type Request struct { - srcName string - xmlName string + srcName string // The Go name of this request. + xmlName string // The XML name of this request. Opcode int - Combine bool - Fields []Field - Reply *Reply + Combine bool // Not currently used. + Fields []Field // All fields in the request. + Reply *Reply // A reply, if one exists for this request. } +// Initialize creates the proper Go source name for this request. +// It also initializes the reply if one exists, and all fields in this request. func (r *Request) Initialize(p *Protocol) { r.srcName = SrcName(p, r.xmlName) if p.Name != "xproto" { @@ -63,6 +42,9 @@ func (r *Request) XmlName() string { return r.xmlName } +// ReplyName gets the Go source name of the function that generates a +// reply type from a slice of bytes. +// The generated function is not currently exported. func (r *Request) ReplyName() string { if r.Reply == nil { log.Panicf("Cannot call 'ReplyName' on request %s, which has no reply.", @@ -73,6 +55,8 @@ func (r *Request) ReplyName() string { return fmt.Sprintf("%sReply", lower) } +// ReplyTypeName gets the Go source name of the type holding all reply data +// for this request. func (r *Request) ReplyTypeName() string { if r.Reply == nil { log.Panicf("Cannot call 'ReplyName' on request %s, which has no reply.", @@ -81,12 +65,17 @@ func (r *Request) ReplyTypeName() string { return fmt.Sprintf("%sReply", r.SrcName()) } +// ReqName gets the Go source name of the function that generates a byte +// slice from a list of parameters. +// The generated function is not currently exported. func (r *Request) ReqName() string { name := r.SrcName() lower := string(unicode.ToLower(rune(name[0]))) + name[1:] return fmt.Sprintf("%sRequest", lower) } +// CookieName gets the Go source name of the type that holds cookies for +// this request. func (r *Request) CookieName() string { return fmt.Sprintf("%sCookie", r.SrcName()) } @@ -99,6 +88,11 @@ func (r *Request) CookieName() string { func (r *Request) Size(c *Context) Size { size := newFixedSize(0) + // If this is a core protocol request, we squeeze in an extra byte of + // data (from the fields below) between the opcode and the size of the + // request. In an extension request, this byte is always occupied + // by the opcode of the request (while the first byte is always occupied + // by the opcode of the extension). if c.protocol.Name == "xproto" { size = size.Add(newFixedSize(3)) } else { @@ -107,7 +101,7 @@ func (r *Request) Size(c *Context) Size { for _, field := range r.Fields { switch field.(type) { - case *LocalField: + case *LocalField: // local fields don't go over the wire continue case *SingleField: // mofos!!! @@ -126,10 +120,16 @@ func (r *Request) Size(c *Context) Size { }) } +// Reply encapsulates the fields associated with a 'reply' element. type Reply struct { Fields []Field } +// Size gets the number of bytes in this request's reply. +// A reply always has at least 7 bytes: +// 1 byte: A reply discriminant (first byte set to 1) +// 2 bytes: A sequence number +// 4 bytes: Number of additional bytes in 4-byte units past initial 32 bytes. func (r *Reply) Size() Size { size := newFixedSize(0) diff --git a/nexgb/xgbgen/size.go b/nexgb/xgbgen/size.go index 70edb8f..d8d3ac3 100644 --- a/nexgb/xgbgen/size.go +++ b/nexgb/xgbgen/size.go @@ -1,21 +1,30 @@ package main +// Size corresponds to an expression that represents the number of bytes +// in some *thing*. Generally, sizes are used to allocate buffers and to +// inform X how big requests are. +// Size is basically a thin layer over an Expression that yields easy methods +// for adding and multiplying sizes. type Size struct { Expression } +// newFixedSize creates a new Size with some fixed and known value. func newFixedSize(fixed uint) Size { return Size{&Value{v: fixed}} } +// newExpressionSize creates a new Size with some expression. func newExpressionSize(variable Expression) Size { return Size{variable} } +// Add adds s1 and s2 and returns a new Size. func (s1 Size) Add(s2 Size) Size { return Size{newBinaryOp("+", s1, s2)} } +// Multiply mupltiplies s1 and s2 and returns a new Size. func (s1 Size) Multiply(s2 Size) Size { return Size{newBinaryOp("*", s1, s2)} } diff --git a/nexgb/xgbgen/xml.go b/nexgb/xgbgen/xml.go index 1b2f89a..df21433 100644 --- a/nexgb/xgbgen/xml.go +++ b/nexgb/xgbgen/xml.go @@ -18,19 +18,19 @@ type XML struct { // Types for all top-level elements. // First are the simple ones. Imports XMLImports `xml:"import"` - Enums XMLEnums `xml:"enum"` - Xids XMLXids `xml:"xidtype"` - XidUnions XMLXids `xml:"xidunion"` - TypeDefs XMLTypeDefs `xml:"typedef"` - EventCopies XMLEventCopies `xml:"eventcopy"` - ErrorCopies XMLErrorCopies `xml:"errorcopy"` + Enums []*XMLEnum `xml:"enum"` + Xids []*XMLXid `xml:"xidtype"` + XidUnions []*XMLXid `xml:"xidunion"` + TypeDefs []*XMLTypeDef `xml:"typedef"` + EventCopies []*XMLEventCopy `xml:"eventcopy"` + ErrorCopies []*XMLErrorCopy `xml:"errorcopy"` // Here are the complex ones, i.e., anything with "structure contents" - Structs XMLStructs `xml:"struct"` - Unions XMLUnions `xml:"union"` - Requests XMLRequests `xml:"request"` - Events XMLEvents `xml:"event"` - Errors XMLErrors `xml:"error"` + Structs []*XMLStruct `xml:"struct"` + Unions []*XMLUnion `xml:"union"` + Requests []*XMLRequest `xml:"request"` + Events []*XMLEvent `xml:"event"` + Errors []*XMLError `xml:"error"` } type XMLImports []*XMLImport @@ -60,8 +60,6 @@ type XMLImport struct { xml *XML `xml:"-"` } -type XMLEnums []XMLEnum - type XMLEnum struct { Name string `xml:"name,attr"` Items []*XMLEnumItem `xml:"item"` @@ -72,77 +70,69 @@ type XMLEnumItem struct { Expr *XMLExpression `xml:",any"` } -type XMLXids []*XMLXid - type XMLXid struct { XMLName xml.Name Name string `xml:"name,attr"` } -type XMLTypeDefs []*XMLTypeDef - type XMLTypeDef struct { Old string `xml:"oldname,attr"` New string `xml:"newname,attr"` } -type XMLEventCopies []*XMLEventCopy - type XMLEventCopy struct { Name string `xml:"name,attr"` Number int `xml:"number,attr"` Ref string `xml:"ref,attr"` } -type XMLErrorCopies []*XMLErrorCopy - type XMLErrorCopy struct { Name string `xml:"name,attr"` Number int `xml:"number,attr"` Ref string `xml:"ref,attr"` } -type XMLStructs []*XMLStruct - type XMLStruct struct { Name string `xml:"name,attr"` - Fields XMLFields `xml:",any"` + Fields []*XMLField `xml:",any"` } -type XMLUnions []*XMLUnion - type XMLUnion struct { Name string `xml:"name,attr"` - Fields XMLFields `xml:",any"` + Fields []*XMLField `xml:",any"` } -type XMLRequests []*XMLRequest - type XMLRequest struct { Name string `xml:"name,attr"` Opcode int `xml:"opcode,attr"` Combine bool `xml:"combine-adjacent,attr"` - Fields XMLFields `xml:",any"` + Fields []*XMLField `xml:",any"` Reply *XMLReply `xml:"reply"` } type XMLReply struct { - Fields XMLFields `xml:",any"` + Fields []*XMLField `xml:",any"` } -type XMLEvents []*XMLEvent - type XMLEvent struct { Name string `xml:"name,attr"` Number int `xml:"number,attr"` NoSequence bool `xml:"no-sequence-number,attr"` - Fields XMLFields `xml:",any"` + Fields []*XMLField `xml:",any"` } -type XMLErrors []*XMLError - type XMLError struct { Name string `xml:"name,attr"` Number int `xml:"number,attr"` - Fields XMLFields `xml:",any"` + Fields []*XMLField `xml:",any"` +} + +type XMLExpression struct { + XMLName xml.Name + + Exprs []*XMLExpression `xml:",any"` + + Data string `xml:",chardata"` + Op string `xml:"op,attr"` + Ref string `xml:"ref,attr"` } diff --git a/nexgb/xgbgen/xml_expression.go b/nexgb/xgbgen/xml_expression.go deleted file mode 100644 index 2989668..0000000 --- a/nexgb/xgbgen/xml_expression.go +++ /dev/null @@ -1,160 +0,0 @@ -package main - -import ( - "encoding/xml" - "fmt" - "log" - "strconv" -) - -type XMLExpression struct { - XMLName xml.Name - - Exprs []*XMLExpression `xml:",any"` - - Data string `xml:",chardata"` - Op string `xml:"op,attr"` - Ref string `xml:"ref,attr"` -} - -func newValueExpression(v uint) *XMLExpression { - return &XMLExpression{ - XMLName: xml.Name{Local: "value"}, - Data: fmt.Sprintf("%d", v), - } -} - -// String is for debugging. For actual use, please use 'Morph'. -func (e *XMLExpression) 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 *XMLExpression) 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 *XMLExpression) BinaryOp(oprnd1, oprnd2 *XMLExpression) *XMLExpression { - 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(oprnd1.Eval() + oprnd2.Eval()) - case "-": - return wrap(oprnd1.Eval() + oprnd2.Eval()) - case "*": - return wrap(oprnd1.Eval() * oprnd2.Eval()) - case "/": - return wrap(oprnd1.Eval() / oprnd2.Eval()) - case "&": - return wrap(oprnd1.Eval() & oprnd2.Eval()) - case "<<": - return wrap(oprnd1.Eval() << oprnd2.Eval()) - } - - log.Panicf("Invalid binary operator '%s' for '%s' expression.", - e.Op, e.XMLName.Local) - panic("unreachable") -} - -func (e *XMLExpression) UnaryOp(oprnd *XMLExpression) *XMLExpression { - 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(^oprnd.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 index 991141b..fe6c5d5 100644 --- a/nexgb/xgbgen/xml_fields.go +++ b/nexgb/xgbgen/xml_fields.go @@ -1,31 +1,10 @@ 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 XMLFields []*XMLField - type XMLField struct { XMLName xml.Name @@ -47,7 +26,7 @@ type XMLField struct { ValueListName string `xml:"value-list-name,attr"` // For 'switch' element. - Bitcases XMLBitcases `xml:"bitcase"` + Bitcases []*XMLBitcase `xml:"bitcase"` // I don't know which elements these are for. The documentation is vague. // They also seem to be completely optional. @@ -56,41 +35,6 @@ type XMLField struct { OptAltEnum string `xml:"altenum,attr"` } -// String is for debugging purposes. -func (f *XMLField) 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") -} - -type XMLBitcases []*XMLBitcase - // 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, @@ -100,7 +44,7 @@ type XMLBitcases []*XMLBitcase // it's the closest thing to a Union I can get to in Go without interfaces. // Would an '' tag have been too much to ask? :-( type XMLBitcase struct { - Fields XMLFields `xml:",any"` + Fields []*XMLField `xml:",any"` // All the different expressions. // When it comes time to choose one, use the 'Expr' method. @@ -114,17 +58,6 @@ type XMLBitcase struct { ExprPop *XMLExpression `xml:"popcount"` } -// StringPrefix is for debugging purposes only. -// StringPrefix takes a string to prefix to every extra line for formatting. -func (b *XMLBitcase) 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 *XMLBitcase) Expr() *XMLExpression {