pulumi/pkg/compiler/parsetree.go
joeduffy 504659c38b Remember stack property values (including bounds)
We need to access the bound property values for a given stack, especially
during code-generation.  This information was present for services before,
however not for stacks constructed via other means (e.g., the top-most one).
This change adds a PropertyValues bag plus a corresponding BoundPropertyValues
to the ast.Stack type.
2016-12-05 10:13:57 -08:00

127 lines
3.9 KiB
Go

// Copyright 2016 Marapongo, Inc. All rights reserved.
package compiler
import (
"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"
)
// PTAnalyzer knows how to walk and validate parse trees.
type PTAnalyzer interface {
core.Visitor
// AnalyzeStack checks the validity of an entire Stack parse tree.
AnalyzeStack(stack *ast.Stack)
// AnalyzeWorkspace checks the validity of an entire Workspace parse tree.
AnalyzeWorkspace(w *ast.Workspace)
}
// NewPTAnalayzer allocates a new PTAnalyzer associated with the given Compiler.
func NewPTAnalyzer(c Compiler) PTAnalyzer {
return &ptAnalyzer{c: c}
}
type ptAnalyzer struct {
c Compiler
}
var _ PTAnalyzer = &ptAnalyzer{} // compile-time assert that binder implements PTAnalyzer.
func (a *ptAnalyzer) Diag() diag.Sink {
return a.c.Diag()
}
func (a *ptAnalyzer) AnalyzeStack(stack *ast.Stack) {
glog.Infof("Parsetree analyzing Mu Stack: %v", stack.Name)
if glog.V(2) {
defer func() {
glog.V(2).Infof("Parsetree analysis for Mu Stack %v completed w/ %v warnings and %v errors",
stack.Name, a.Diag().Warnings(), a.Diag().Errors())
}()
}
// Use an InOrderVisitor to walk the tree in-order; this handles determinism for us.
v := core.NewInOrderVisitor(a, nil)
v.VisitStack(stack)
}
func (a *ptAnalyzer) AnalyzeWorkspace(w *ast.Workspace) {
glog.Infof("Parsetree analyzing workspace file: %v", w.Doc.File)
if glog.V(2) {
defer func() {
glog.V(2).Infof("Parsetree analysis for workspace %v completed w/ %v warnings and %v errors",
w.Doc.File, a.Diag().Warnings(), a.Diag().Errors())
}()
}
// Use an InOrderVisitor to walk the tree in-order; this handles determinism for us.
v := core.NewInOrderVisitor(a, nil)
v.VisitWorkspace(w)
}
func (a *ptAnalyzer) VisitWorkspace(w *ast.Workspace) {
}
func (a *ptAnalyzer) VisitCluster(name string, cluster *ast.Cluster) {
// Decorate the AST with contextual information so subsequent passes can operate context-free.
cluster.Name = name
}
func (a *ptAnalyzer) VisitDependency(parent *ast.Workspace, ref ast.Ref, dep *ast.Dependency) {
}
func (a *ptAnalyzer) VisitStack(stack *ast.Stack) {
}
func (a *ptAnalyzer) VisitProperty(parent *ast.Stack, name string, param *ast.Property) {
// Decorate the AST with contextual information so subsequent passes can operate context-free.
param.Name = name
}
func (a *ptAnalyzer) VisitServices(parent *ast.Stack, svcs *ast.Services) {
// We need to expand the UntypedServiceMaps into strongly typed ServiceMaps. As part of this, we also decorate the
// AST with extra contextual information so subsequent passes can operate context-free.
// TODO[marapongo/mu#4]: once we harden the marshalers, we should be able to largely eliminate this.
svcs.Public = make(ast.ServiceMap)
for _, name := range ast.StableUntypedServices(svcs.PublicUntyped) {
svcs.Public[name] = a.untypedServiceToTyped(parent, name, true, svcs.PublicUntyped[name])
}
svcs.Private = make(ast.ServiceMap)
for _, name := range ast.StableUntypedServices(svcs.PrivateUntyped) {
svcs.Private[name] = a.untypedServiceToTyped(parent, name, false, svcs.PrivateUntyped[name])
}
}
func (a *ptAnalyzer) untypedServiceToTyped(parent *ast.Stack, name ast.Name, public bool,
bag map[string]interface{}) *ast.Service {
var typ ast.Name
t, has := bag["type"]
if has {
// If the bag contains a type, ensure that it is a string.
ts, ok := t.(string)
if ok {
typ = ast.Name(ts)
} else {
a.Diag().Errorf(errors.ErrorIllegalMufileSyntax.At(parent), "service type must be a string")
}
// Delete the type property so it's not considered semantically meaningful for the target.
delete(bag, "type")
}
return &ast.Service{
Name: name,
Type: ast.Ref(typ),
Public: public,
Properties: bag,
}
}
func (a *ptAnalyzer) VisitService(pstack *ast.Stack, parent *ast.Services, name ast.Name, public bool,
svc *ast.Service) {
}