In some cases, we want to specialize template generation based on the options passed to the compiler. This change flows them through so that they can be accessed as {{if .Options.SomeSetting}} ... {{end}}
127 lines
3.4 KiB
Go
127 lines
3.4 KiB
Go
// Copyright 2016 Marapongo, Inc. All rights reserved.
|
|
|
|
package compiler
|
|
|
|
import (
|
|
"os"
|
|
|
|
"github.com/golang/glog"
|
|
|
|
"github.com/marapongo/mu/pkg/ast"
|
|
"github.com/marapongo/mu/pkg/compiler/core"
|
|
"github.com/marapongo/mu/pkg/diag"
|
|
"github.com/marapongo/mu/pkg/errors"
|
|
"github.com/marapongo/mu/pkg/util"
|
|
"github.com/marapongo/mu/pkg/workspace"
|
|
)
|
|
|
|
// Compiler provides an interface into the many phases of the Mu compilation process.
|
|
type Compiler interface {
|
|
core.Phase
|
|
|
|
// Context returns the current compiler context.
|
|
Context() *Context
|
|
|
|
// Build detects and compiles inputs from the given location, storing build artifacts in the given destination.
|
|
Build(inp string, outp string)
|
|
// BuildFile uses the given Mufile directly, and stores build artifacts in the given destination.
|
|
BuildFile(mufile []byte, ext string, outp string)
|
|
}
|
|
|
|
// compiler is the canonical implementation of the Mu compiler.
|
|
type compiler struct {
|
|
opts *Options
|
|
ctx *Context
|
|
deps map[ast.Ref]*diag.Document // a cache of mapping names to loaded dependencies.
|
|
}
|
|
|
|
// NewCompiler creates a new instance of the Mu compiler, with the given initialization settings.
|
|
func NewCompiler(opts *Options) Compiler {
|
|
return &compiler{
|
|
opts: opts,
|
|
ctx: NewContext(opts),
|
|
deps: make(map[ast.Ref]*diag.Document),
|
|
}
|
|
}
|
|
|
|
func (c *compiler) Context() *Context {
|
|
return c.ctx
|
|
}
|
|
|
|
func (c *compiler) Diag() diag.Sink {
|
|
return c.opts.Diag
|
|
}
|
|
|
|
func (c *compiler) Build(inp string, outp string) {
|
|
glog.Infof("Building target '%v' (out='%v')", inp, outp)
|
|
|
|
// First find the root of the current package based on the location of its Mufile.
|
|
w, err := workspace.New(inp, c.Diag())
|
|
if err != nil {
|
|
c.Diag().Errorf(errors.ErrorIO.AtFile(inp), err)
|
|
return
|
|
}
|
|
|
|
// Now actually locate, load, and parse the Mufile.
|
|
mufile, err := w.DetectMufile()
|
|
if err != nil {
|
|
c.Diag().Errorf(errors.ErrorIO.AtFile(inp), err)
|
|
return
|
|
}
|
|
if mufile == "" {
|
|
c.Diag().Errorf(errors.ErrorMissingMufile, inp)
|
|
return
|
|
}
|
|
|
|
// Read in the contents of the document and make it available to subsequent stages.
|
|
doc, err := diag.ReadDocument(mufile)
|
|
if err != nil {
|
|
c.Diag().Errorf(errors.ErrorCouldNotReadMufile.AtFile(mufile), err)
|
|
return
|
|
}
|
|
|
|
c.buildDocument(w, doc, outp)
|
|
}
|
|
|
|
func (c *compiler) BuildFile(mufile []byte, ext string, outp string) {
|
|
glog.Infof("Building in-memory %v file (bytes=%v out='%v')", ext, len(mufile), outp)
|
|
|
|
// Default to the current working directory for the workspace.
|
|
dir, err := os.Getwd()
|
|
if err != nil {
|
|
c.Diag().Errorf(errors.ErrorIO, err)
|
|
return
|
|
}
|
|
w, err := workspace.New(dir, c.Diag())
|
|
if err != nil {
|
|
c.Diag().Errorf(errors.ErrorIO, err)
|
|
return
|
|
}
|
|
|
|
doc := &diag.Document{File: workspace.Mufile + ext, Body: mufile}
|
|
c.buildDocument(w, doc, outp)
|
|
}
|
|
|
|
func (c *compiler) buildDocument(w workspace.W, doc *diag.Document, outp string) {
|
|
glog.Infof("Building doc '%v' (bytes=%v out='%v')", doc.File, len(doc.Body), outp)
|
|
if glog.V(2) {
|
|
defer glog.V(2).Infof("Building doc '%v' completed w/ %v warnings and %v errors",
|
|
doc.File, c.Diag().Warnings(), c.Diag().Errors())
|
|
}
|
|
|
|
// Perform the front-end phases of the compiler.
|
|
stack := c.buildDocumentFE(w, doc)
|
|
if !c.Diag().Success() {
|
|
return
|
|
}
|
|
util.Assert(stack != nil)
|
|
|
|
// Next, perform the semantic analysis phases of the compiler.
|
|
c.buildDocumentSema(w, stack)
|
|
if !c.Diag().Success() {
|
|
return
|
|
}
|
|
|
|
// Finally, perform the back-end phases of the compiler.
|
|
c.buildDocumentBE(w, stack)
|
|
}
|