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