1package build 2 3import ( 4 "errors" 5 "fmt" 6 "go/types" 7 8 "golang.org/x/tools/go/packages" 9 10 "github.com/mmcloughlin/avo/attr" 11 "github.com/mmcloughlin/avo/buildtags" 12 "github.com/mmcloughlin/avo/gotypes" 13 "github.com/mmcloughlin/avo/ir" 14 "github.com/mmcloughlin/avo/operand" 15 "github.com/mmcloughlin/avo/reg" 16) 17 18// Context maintains state for incrementally building an avo File. 19type Context struct { 20 pkg *packages.Package 21 file *ir.File 22 function *ir.Function 23 global *ir.Global 24 errs ErrorList 25 reg.Collection 26} 27 28// NewContext initializes an empty build Context. 29func NewContext() *Context { 30 return &Context{ 31 file: ir.NewFile(), 32 Collection: *reg.NewCollection(), 33 } 34} 35 36// Package sets the package the generated file will belong to. Required to be able to reference types in the package. 37func (c *Context) Package(path string) { 38 cfg := &packages.Config{ 39 Mode: packages.NeedTypes | packages.NeedDeps | packages.NeedImports, 40 } 41 pkgs, err := packages.Load(cfg, path) 42 if err != nil { 43 c.adderror(err) 44 return 45 } 46 pkg := pkgs[0] 47 if len(pkg.Errors) > 0 { 48 for _, err := range pkg.Errors { 49 c.adderror(err) 50 } 51 return 52 } 53 c.pkg = pkg 54} 55 56// Constraints sets build constraints for the file. 57func (c *Context) Constraints(t buildtags.ConstraintsConvertable) { 58 cs := t.ToConstraints() 59 if err := cs.Validate(); err != nil { 60 c.adderror(err) 61 return 62 } 63 c.file.Constraints = cs 64} 65 66// Constraint appends a constraint to the file's build constraints. 67func (c *Context) Constraint(t buildtags.ConstraintConvertable) { 68 c.Constraints(append(c.file.Constraints, t.ToConstraint())) 69} 70 71// ConstraintExpr appends a constraint to the file's build constraints. The 72// constraint to add is parsed from the given expression. The expression should 73// look the same as the content following "// +build " in regular build 74// constraint comments. 75func (c *Context) ConstraintExpr(expr string) { 76 constraint, err := buildtags.ParseConstraint(expr) 77 if err != nil { 78 c.adderror(err) 79 return 80 } 81 c.Constraint(constraint) 82} 83 84// Function starts building a new function with the given name. 85func (c *Context) Function(name string) { 86 c.function = ir.NewFunction(name) 87 c.file.AddSection(c.function) 88} 89 90// Doc sets documentation comment lines for the currently active function. 91func (c *Context) Doc(lines ...string) { 92 c.activefunc().Doc = lines 93} 94 95// Pragma adds a compiler directive to the currently active function. 96func (c *Context) Pragma(directive string, args ...string) { 97 c.activefunc().AddPragma(directive, args...) 98} 99 100// Attributes sets function attributes for the currently active function. 101func (c *Context) Attributes(a attr.Attribute) { 102 c.activefunc().Attributes = a 103} 104 105// Signature sets the signature for the currently active function. 106func (c *Context) Signature(s *gotypes.Signature) { 107 c.activefunc().SetSignature(s) 108} 109 110// SignatureExpr parses the signature expression and sets it as the active function's signature. 111func (c *Context) SignatureExpr(expr string) { 112 s, err := gotypes.ParseSignatureInPackage(c.types(), expr) 113 if err != nil { 114 c.adderror(err) 115 return 116 } 117 c.Signature(s) 118} 119 120// Implement starts building a function of the given name, whose type is 121// specified by a stub in the containing package. 122func (c *Context) Implement(name string) { 123 pkg := c.types() 124 if pkg == nil { 125 c.adderrormessage("no package specified") 126 return 127 } 128 s, err := gotypes.LookupSignature(pkg, name) 129 if err != nil { 130 c.adderror(err) 131 return 132 } 133 c.Function(name) 134 c.Signature(s) 135} 136 137func (c *Context) types() *types.Package { 138 if c.pkg == nil { 139 return nil 140 } 141 return c.pkg.Types 142} 143 144// AllocLocal allocates size bytes in the stack of the currently active function. 145// Returns a reference to the base pointer for the newly allocated region. 146func (c *Context) AllocLocal(size int) operand.Mem { 147 return c.activefunc().AllocLocal(size) 148} 149 150// Instruction adds an instruction to the active function. 151func (c *Context) Instruction(i *ir.Instruction) { 152 c.activefunc().AddInstruction(i) 153} 154 155// Label adds a label to the active function. 156func (c *Context) Label(name string) { 157 c.activefunc().AddLabel(ir.Label(name)) 158} 159 160// Comment adds comment lines to the active function. 161func (c *Context) Comment(lines ...string) { 162 c.activefunc().AddComment(lines...) 163} 164 165// Commentf adds a formtted comment line. 166func (c *Context) Commentf(format string, a ...interface{}) { 167 c.Comment(fmt.Sprintf(format, a...)) 168} 169 170func (c *Context) activefunc() *ir.Function { 171 if c.function == nil { 172 c.adderrormessage("no active function") 173 return ir.NewFunction("") 174 } 175 return c.function 176} 177 178//go:generate avogen -output zinstructions.go build 179 180// StaticGlobal adds a new static data section to the file and returns a pointer to it. 181func (c *Context) StaticGlobal(name string) operand.Mem { 182 c.global = ir.NewStaticGlobal(name) 183 c.file.AddSection(c.global) 184 return c.global.Base() 185} 186 187// DataAttributes sets the attributes on the current active global data section. 188func (c *Context) DataAttributes(a attr.Attribute) { 189 c.activeglobal().Attributes = a 190} 191 192// AddDatum adds constant v at offset to the current active global data section. 193func (c *Context) AddDatum(offset int, v operand.Constant) { 194 if err := c.activeglobal().AddDatum(ir.NewDatum(offset, v)); err != nil { 195 c.adderror(err) 196 } 197} 198 199// AppendDatum appends a constant to the current active global data section. 200func (c *Context) AppendDatum(v operand.Constant) { 201 c.activeglobal().Append(v) 202} 203 204func (c *Context) activeglobal() *ir.Global { 205 if c.global == nil { 206 c.adderrormessage("no active global") 207 return ir.NewStaticGlobal("") 208 } 209 return c.global 210} 211 212func (c *Context) adderror(err error) { 213 c.errs.addext(err) 214} 215 216func (c *Context) adderrormessage(msg string) { 217 c.adderror(errors.New(msg)) 218} 219 220// Result returns the built file and any accumulated errors. 221func (c *Context) Result() (*ir.File, error) { 222 return c.file, c.errs.Err() 223} 224