2016-11-16 18:29:44 +01:00
|
|
|
// Copyright 2016 Marapongo, Inc. All rights reserved.
|
|
|
|
|
|
|
|
package compiler
|
|
|
|
|
|
|
|
import (
|
|
|
|
"github.com/golang/glog"
|
|
|
|
|
|
|
|
"github.com/marapongo/mu/pkg/ast"
|
2016-11-17 17:52:54 +01:00
|
|
|
"github.com/marapongo/mu/pkg/compiler/core"
|
2016-11-19 03:12:26 +01:00
|
|
|
"github.com/marapongo/mu/pkg/compiler/predef"
|
2016-11-16 18:29:44 +01:00
|
|
|
"github.com/marapongo/mu/pkg/diag"
|
2016-11-16 22:11:58 +01:00
|
|
|
"github.com/marapongo/mu/pkg/errors"
|
2016-11-20 01:13:13 +01:00
|
|
|
"github.com/marapongo/mu/pkg/util"
|
2016-11-16 18:29:44 +01:00
|
|
|
)
|
|
|
|
|
2016-11-16 20:09:45 +01:00
|
|
|
// Binder annotates an existing parse tree with semantic information.
|
2016-11-16 18:29:44 +01:00
|
|
|
type Binder interface {
|
2016-11-17 17:52:54 +01:00
|
|
|
core.Phase
|
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
// PrepareStack prepares the AST for binding.
|
|
|
|
PrepareStack(stack *ast.Stack)
|
|
|
|
// BindStack takes an AST and binds all names inside of it, mutating it in place.
|
|
|
|
BindStack(stack *ast.Stack)
|
2016-11-16 18:29:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewBinder(c Compiler) Binder {
|
2016-11-17 15:10:23 +01:00
|
|
|
// Create a new binder and a new scope with an empty symbol table.
|
2016-11-17 14:36:58 +01:00
|
|
|
b := &binder{c: c}
|
|
|
|
b.PushScope()
|
2016-11-17 15:10:23 +01:00
|
|
|
|
|
|
|
// And now populate that symbol table with all known predefined Stack types before returning it.
|
2016-11-19 19:22:56 +01:00
|
|
|
for nm, stack := range predef.Stacks {
|
2016-11-17 15:10:23 +01:00
|
|
|
sym := NewStackSymbol(nm, stack)
|
2016-11-20 01:13:13 +01:00
|
|
|
ok := b.RegisterSymbol(sym)
|
|
|
|
util.AssertMF(ok, "Unexpected Symbol collision when registering predef Stack type %v", nm)
|
2016-11-17 15:10:23 +01:00
|
|
|
}
|
|
|
|
|
2016-11-17 14:36:58 +01:00
|
|
|
return b
|
2016-11-16 18:29:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type binder struct {
|
2016-11-17 14:36:58 +01:00
|
|
|
c Compiler
|
|
|
|
scope *scope
|
2016-11-16 18:29:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (b *binder) Diag() diag.Sink {
|
|
|
|
return b.c.Diag()
|
|
|
|
}
|
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
func (b *binder) PrepareStack(stack *ast.Stack) {
|
|
|
|
glog.Infof("Preparing Mu Stack: %v", stack.Name)
|
2016-11-16 18:29:44 +01:00
|
|
|
if glog.V(2) {
|
|
|
|
defer func() {
|
2016-11-23 16:26:45 +01:00
|
|
|
glog.V(2).Infof("Preparing Mu Stack %v completed w/ %v warnings and %v errors",
|
2016-11-16 18:29:44 +01:00
|
|
|
stack.Name, b.Diag().Warnings(), b.Diag().Errors())
|
|
|
|
}()
|
|
|
|
}
|
2016-11-16 22:11:58 +01:00
|
|
|
|
2016-11-17 15:10:23 +01:00
|
|
|
// Push a new scope for this binding pass.
|
|
|
|
b.PushScope()
|
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
// Now perform a phase1 walk of the tree, preparing it for subsequent binding. This must be done as a
|
|
|
|
// separate phase because we won't know what to stick into the symbol table until after this first walk.
|
|
|
|
phase1 := &binderPhase1{b: b, top: stack}
|
2016-11-17 17:52:54 +01:00
|
|
|
v1 := core.NewInOrderVisitor(phase1, nil)
|
2016-11-23 16:26:45 +01:00
|
|
|
v1.VisitStack(stack)
|
|
|
|
}
|
2016-11-16 22:11:58 +01:00
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
func (b *binder) BindStack(stack *ast.Stack) {
|
|
|
|
glog.Infof("Binding Mu Stack: %v", stack.Name)
|
|
|
|
if glog.V(2) {
|
|
|
|
defer func() {
|
|
|
|
glog.V(2).Infof("Binding Mu Stack %v completed w/ %v warnings and %v errors",
|
|
|
|
stack.Name, b.Diag().Warnings(), b.Diag().Errors())
|
|
|
|
}()
|
2016-11-17 03:55:20 +01:00
|
|
|
}
|
2016-11-23 16:26:45 +01:00
|
|
|
|
|
|
|
// Restore the original scope after this binding pass.
|
|
|
|
defer b.PopScope()
|
|
|
|
|
|
|
|
// Now perform a phase2 walk of the tree, completing the binding process. The 1st walk will have given
|
|
|
|
// us everything we need for a fully populated symbol table, so that type binding will resolve correctly.
|
|
|
|
phase2 := &binderPhase2{b: b, top: stack}
|
|
|
|
v2 := core.NewInOrderVisitor(phase2, nil)
|
|
|
|
v2.VisitStack(stack)
|
2016-11-17 03:55:20 +01:00
|
|
|
}
|
|
|
|
|
2016-11-17 04:08:41 +01:00
|
|
|
// LookupStack binds a name to a Stack type.
|
2016-11-17 03:55:20 +01:00
|
|
|
func (b *binder) LookupStack(nm ast.Name) (*Symbol, *ast.Stack) {
|
2016-11-20 01:13:13 +01:00
|
|
|
util.AssertM(b.scope != nil, "Unexpected empty binding scope during LookupStack")
|
2016-11-17 14:36:58 +01:00
|
|
|
return b.scope.LookupStack(nm)
|
|
|
|
}
|
|
|
|
|
2016-11-25 21:58:29 +01:00
|
|
|
// LookupDocument binds a name to a Document type.
|
|
|
|
func (b *binder) LookupDocument(nm ast.Name) (*Symbol, *diag.Document) {
|
|
|
|
util.AssertM(b.scope != nil, "Unexpected empty binding scope during LookupDocument")
|
|
|
|
return b.scope.LookupDocument(nm)
|
|
|
|
}
|
|
|
|
|
2016-11-17 14:36:58 +01:00
|
|
|
// LookupSymbol binds a name to any kind of Symbol.
|
|
|
|
func (b *binder) LookupSymbol(nm ast.Name) *Symbol {
|
2016-11-20 01:13:13 +01:00
|
|
|
util.AssertM(b.scope != nil, "Unexpected empty binding scope during LookupSymbol")
|
2016-11-17 14:36:58 +01:00
|
|
|
return b.scope.LookupSymbol(nm)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RegisterSymbol registers a symbol with the given name; if it already exists, the function returns false.
|
|
|
|
func (b *binder) RegisterSymbol(sym *Symbol) bool {
|
2016-11-20 01:13:13 +01:00
|
|
|
util.AssertM(b.scope != nil, "Unexpected empty binding scope during RegisterSymbol")
|
2016-11-17 14:36:58 +01:00
|
|
|
return b.scope.RegisterSymbol(sym)
|
|
|
|
}
|
|
|
|
|
|
|
|
// PushScope creates a new scope with an empty symbol table parented to the existing one.
|
|
|
|
func (b *binder) PushScope() {
|
|
|
|
b.scope = &scope{parent: b.scope, symtbl: make(map[ast.Name]*Symbol)}
|
|
|
|
}
|
|
|
|
|
|
|
|
// PopScope replaces the current scope with its parent.
|
|
|
|
func (b *binder) PopScope() {
|
2016-11-20 01:13:13 +01:00
|
|
|
util.AssertM(b.scope != nil, "Unexpected empty binding scope during pop")
|
2016-11-17 14:36:58 +01:00
|
|
|
b.scope = b.scope.parent
|
|
|
|
}
|
|
|
|
|
|
|
|
// scope enables lookups and symbols to obey traditional language scoping rules.
|
|
|
|
type scope struct {
|
|
|
|
parent *scope
|
|
|
|
symtbl map[ast.Name]*Symbol
|
|
|
|
}
|
|
|
|
|
|
|
|
// LookupStack binds a name to a Stack type.
|
|
|
|
func (s *scope) LookupStack(nm ast.Name) (*Symbol, *ast.Stack) {
|
|
|
|
sym := s.LookupSymbol(nm)
|
2016-11-17 03:55:20 +01:00
|
|
|
if sym != nil && sym.Kind == SymKindStack {
|
|
|
|
return sym, sym.Real.(*ast.Stack)
|
|
|
|
}
|
2016-11-17 14:36:58 +01:00
|
|
|
// TODO: we probably need to issue an error for this condition (wrong expected symbol type).
|
2016-11-17 03:55:20 +01:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2016-11-25 21:58:29 +01:00
|
|
|
// LookupDocument binds a name to a Document type.
|
|
|
|
func (s *scope) LookupDocument(nm ast.Name) (*Symbol, *diag.Document) {
|
|
|
|
sym := s.LookupSymbol(nm)
|
|
|
|
if sym != nil && sym.Kind == SymKindDocument {
|
|
|
|
return sym, sym.Real.(*diag.Document)
|
|
|
|
}
|
|
|
|
// TODO: we probably need to issue an error for this condition (wrong expected symbol type).
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2016-11-17 04:08:41 +01:00
|
|
|
// LookupSymbol binds a name to any kind of Symbol.
|
2016-11-17 14:36:58 +01:00
|
|
|
func (s *scope) LookupSymbol(nm ast.Name) *Symbol {
|
|
|
|
for s != nil {
|
|
|
|
if sym, exists := s.symtbl[nm]; exists {
|
|
|
|
return sym
|
|
|
|
}
|
|
|
|
s = s.parent
|
|
|
|
}
|
|
|
|
return nil
|
2016-11-16 22:11:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// RegisterSymbol registers a symbol with the given name; if it already exists, the function returns false.
|
2016-11-17 14:36:58 +01:00
|
|
|
func (s *scope) RegisterSymbol(sym *Symbol) bool {
|
2016-11-16 22:11:58 +01:00
|
|
|
nm := sym.Name
|
2016-11-17 14:36:58 +01:00
|
|
|
if _, exists := s.symtbl[nm]; exists {
|
|
|
|
// TODO: this won't catch "shadowing" for parent scopes; do we care about this?
|
2016-11-16 22:11:58 +01:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2016-11-17 14:36:58 +01:00
|
|
|
s.symtbl[nm] = sym
|
2016-11-16 22:11:58 +01:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
type binderPhase1 struct {
|
|
|
|
b *binder
|
2016-11-23 16:26:45 +01:00
|
|
|
top *ast.Stack
|
2016-11-16 22:11:58 +01:00
|
|
|
}
|
|
|
|
|
Implement dependency versions
This change implements dependency versions, including semantic analysis, per the
checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0.
There's quite a bit in here but at a top-level this parses and validates dependency
references of the form
[[proto://]base.url]namespace/.../name[@version]
and verifies that the components are correct, as well as binding them to symbols.
These references can appear in two places at the moment:
* Service types.
* Cluster dependencies.
As part of this change, a number of supporting changes have been made:
* Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis.
This allows us to share logic around the validation of common AST types. This also
moves some of the logic around loading workspace.yaml files back to the parser, where
it can be unified with the way we load Mu.yaml files.
* New ast.Version and ast.VersionSpec types. The former represents a precise version
-- either a specific semantic version or a short or long Git SHA hash -- and the
latter represents a range -- either a Version, "latest", or a semantic range.
* New ast.Ref and ast.RefParts types. The former is an unparsed string that is
thought to contain a Ref, while the latter is a validated Ref that has been parsed
into its components (Proto, Base, Name, and Version).
* Added some type assertions to ensure certain structs implement certain interfaces,
to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.)
* Be consistent about prefixing error types with Error or Warning.
* Organize the core compiler driver's logic into three methods, FE, sema, and BE.
* A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
|
|
|
var _ core.Visitor = &binderPhase1{} // compile-time assertion that the binder implements core.Visitor.
|
|
|
|
|
2016-11-16 22:11:58 +01:00
|
|
|
func (p *binderPhase1) Diag() diag.Sink {
|
|
|
|
return p.b.Diag()
|
|
|
|
}
|
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
func (p *binderPhase1) VisitWorkspace(workspace *ast.Workspace) {
|
2016-11-16 22:11:58 +01:00
|
|
|
}
|
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
func (p *binderPhase1) VisitCluster(name string, cluster *ast.Cluster) {
|
2016-11-16 22:11:58 +01:00
|
|
|
}
|
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
func (p *binderPhase1) VisitDependency(parent *ast.Workspace, ref ast.Ref, dep *ast.Dependency) {
|
|
|
|
// Workspace dependencies must use legal version specs; validate that this parses now so that we can use it
|
|
|
|
// later on without needing to worry about additional validation.
|
Implement dependency versions
This change implements dependency versions, including semantic analysis, per the
checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0.
There's quite a bit in here but at a top-level this parses and validates dependency
references of the form
[[proto://]base.url]namespace/.../name[@version]
and verifies that the components are correct, as well as binding them to symbols.
These references can appear in two places at the moment:
* Service types.
* Cluster dependencies.
As part of this change, a number of supporting changes have been made:
* Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis.
This allows us to share logic around the validation of common AST types. This also
moves some of the logic around loading workspace.yaml files back to the parser, where
it can be unified with the way we load Mu.yaml files.
* New ast.Version and ast.VersionSpec types. The former represents a precise version
-- either a specific semantic version or a short or long Git SHA hash -- and the
latter represents a range -- either a Version, "latest", or a semantic range.
* New ast.Ref and ast.RefParts types. The former is an unparsed string that is
thought to contain a Ref, while the latter is a validated Ref that has been parsed
into its components (Proto, Base, Name, and Version).
* Added some type assertions to ensure certain structs implement certain interfaces,
to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.)
* Be consistent about prefixing error types with Error or Warning.
* Organize the core compiler driver's logic into three methods, FE, sema, and BE.
* A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
|
|
|
_, err := ref.Parse()
|
|
|
|
if err != nil {
|
2016-11-23 16:54:40 +01:00
|
|
|
p.Diag().Errorf(errors.ErrorMalformedStackReference.At(parent), ref, err)
|
Implement dependency versions
This change implements dependency versions, including semantic analysis, per the
checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0.
There's quite a bit in here but at a top-level this parses and validates dependency
references of the form
[[proto://]base.url]namespace/.../name[@version]
and verifies that the components are correct, as well as binding them to symbols.
These references can appear in two places at the moment:
* Service types.
* Cluster dependencies.
As part of this change, a number of supporting changes have been made:
* Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis.
This allows us to share logic around the validation of common AST types. This also
moves some of the logic around loading workspace.yaml files back to the parser, where
it can be unified with the way we load Mu.yaml files.
* New ast.Version and ast.VersionSpec types. The former represents a precise version
-- either a specific semantic version or a short or long Git SHA hash -- and the
latter represents a range -- either a Version, "latest", or a semantic range.
* New ast.Ref and ast.RefParts types. The former is an unparsed string that is
thought to contain a Ref, while the latter is a validated Ref that has been parsed
into its components (Proto, Base, Name, and Version).
* Added some type assertions to ensure certain structs implement certain interfaces,
to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.)
* Be consistent about prefixing error types with Error or Warning.
* Organize the core compiler driver's logic into three methods, FE, sema, and BE.
* A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
// registerDependencyToBind adds a dependency that needs to be resolved/bound before phase 2 occurs.
|
|
|
|
func (p *binderPhase1) registerDependencyToBind(stack *ast.Stack, ref ast.Ref) (ast.RefParts, bool) {
|
|
|
|
util.Assert(stack.BoundDependencies != nil)
|
|
|
|
|
|
|
|
ty, err := ref.Parse()
|
|
|
|
if err == nil {
|
|
|
|
// Ensure that empty RefParts are canonicalized in the key we use so we don't add duplicates.
|
|
|
|
ref = ty.Defaults().Ref()
|
|
|
|
|
|
|
|
if _, exist := stack.BoundDependencies[ref]; !exist {
|
|
|
|
// If a stack symbol exists for this name, use it so that the compiler needn't resolve it. Else, nil.
|
|
|
|
nm := ty.Name
|
|
|
|
_, st := p.b.LookupStack(nm)
|
|
|
|
|
|
|
|
stack.BoundDependencies[ref] = ast.BoundDependency{
|
|
|
|
Ref: ty,
|
|
|
|
Stack: st,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ty, true
|
|
|
|
}
|
|
|
|
|
2016-11-23 16:54:40 +01:00
|
|
|
p.Diag().Errorf(errors.ErrorMalformedStackReference.At(stack), ref, err)
|
2016-11-23 16:26:45 +01:00
|
|
|
return ty, false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *binderPhase1) VisitStack(stack *ast.Stack) {
|
|
|
|
// Make room for bound dependencies.
|
|
|
|
util.Assert(stack.BoundDependencies == nil)
|
|
|
|
stack.BoundDependencies = make(ast.BoundDependencies)
|
|
|
|
|
|
|
|
// If the stack has a base type, we must add it as a bound dependency.
|
|
|
|
if stack.Base != "" {
|
|
|
|
p.registerDependencyToBind(stack, stack.Base)
|
|
|
|
}
|
|
|
|
|
Implement dependency versions
This change implements dependency versions, including semantic analysis, per the
checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0.
There's quite a bit in here but at a top-level this parses and validates dependency
references of the form
[[proto://]base.url]namespace/.../name[@version]
and verifies that the components are correct, as well as binding them to symbols.
These references can appear in two places at the moment:
* Service types.
* Cluster dependencies.
As part of this change, a number of supporting changes have been made:
* Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis.
This allows us to share logic around the validation of common AST types. This also
moves some of the logic around loading workspace.yaml files back to the parser, where
it can be unified with the way we load Mu.yaml files.
* New ast.Version and ast.VersionSpec types. The former represents a precise version
-- either a specific semantic version or a short or long Git SHA hash -- and the
latter represents a range -- either a Version, "latest", or a semantic range.
* New ast.Ref and ast.RefParts types. The former is an unparsed string that is
thought to contain a Ref, while the latter is a validated Ref that has been parsed
into its components (Proto, Base, Name, and Version).
* Added some type assertions to ensure certain structs implement certain interfaces,
to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.)
* Be consistent about prefixing error types with Error or Warning.
* Organize the core compiler driver's logic into three methods, FE, sema, and BE.
* A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
|
|
|
// Stack names are required.
|
|
|
|
if stack.Name == "" {
|
2016-11-23 16:54:40 +01:00
|
|
|
p.Diag().Errorf(errors.ErrorMissingStackName.At(stack))
|
Implement dependency versions
This change implements dependency versions, including semantic analysis, per the
checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0.
There's quite a bit in here but at a top-level this parses and validates dependency
references of the form
[[proto://]base.url]namespace/.../name[@version]
and verifies that the components are correct, as well as binding them to symbols.
These references can appear in two places at the moment:
* Service types.
* Cluster dependencies.
As part of this change, a number of supporting changes have been made:
* Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis.
This allows us to share logic around the validation of common AST types. This also
moves some of the logic around loading workspace.yaml files back to the parser, where
it can be unified with the way we load Mu.yaml files.
* New ast.Version and ast.VersionSpec types. The former represents a precise version
-- either a specific semantic version or a short or long Git SHA hash -- and the
latter represents a range -- either a Version, "latest", or a semantic range.
* New ast.Ref and ast.RefParts types. The former is an unparsed string that is
thought to contain a Ref, while the latter is a validated Ref that has been parsed
into its components (Proto, Base, Name, and Version).
* Added some type assertions to ensure certain structs implement certain interfaces,
to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.)
* Be consistent about prefixing error types with Error or Warning.
* Organize the core compiler driver's logic into three methods, FE, sema, and BE.
* A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Stack versions must be valid semantic versions (and specifically, not ranges). In other words, we need
|
|
|
|
// a concrete version number like "1.3.9-beta2" and *not* a range like ">1.3.9".
|
|
|
|
// TODO: should we require a version number?
|
|
|
|
if stack.Version != "" {
|
|
|
|
if err := stack.Version.Check(); err != nil {
|
2016-11-23 16:54:40 +01:00
|
|
|
p.Diag().Errorf(errors.ErrorIllegalStackVersion.At(stack), stack.Version, err)
|
Implement dependency versions
This change implements dependency versions, including semantic analysis, per the
checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0.
There's quite a bit in here but at a top-level this parses and validates dependency
references of the form
[[proto://]base.url]namespace/.../name[@version]
and verifies that the components are correct, as well as binding them to symbols.
These references can appear in two places at the moment:
* Service types.
* Cluster dependencies.
As part of this change, a number of supporting changes have been made:
* Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis.
This allows us to share logic around the validation of common AST types. This also
moves some of the logic around loading workspace.yaml files back to the parser, where
it can be unified with the way we load Mu.yaml files.
* New ast.Version and ast.VersionSpec types. The former represents a precise version
-- either a specific semantic version or a short or long Git SHA hash -- and the
latter represents a range -- either a Version, "latest", or a semantic range.
* New ast.Ref and ast.RefParts types. The former is an unparsed string that is
thought to contain a Ref, while the latter is a validated Ref that has been parsed
into its components (Proto, Base, Name, and Version).
* Added some type assertions to ensure certain structs implement certain interfaces,
to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.)
* Be consistent about prefixing error types with Error or Warning.
* Organize the core compiler driver's logic into three methods, FE, sema, and BE.
* A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
|
|
|
}
|
|
|
|
}
|
2016-11-16 22:11:58 +01:00
|
|
|
}
|
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
func (p *binderPhase1) VisitProperty(parent *ast.Stack, name string, param *ast.Property) {
|
2016-11-17 02:30:03 +01:00
|
|
|
}
|
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
func (p *binderPhase1) VisitServices(parent *ast.Stack, svcs *ast.Services) {
|
2016-11-17 02:30:03 +01:00
|
|
|
}
|
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
func (p *binderPhase1) VisitService(pstack *ast.Stack, parent *ast.Services, name ast.Name,
|
|
|
|
public bool, svc *ast.Service) {
|
2016-11-16 22:11:58 +01:00
|
|
|
// Each service has a type. There are two forms of specifying a type, and this phase will normalize this to a
|
|
|
|
// single canonical form to simplify subsequent phases. First, there is a shorthand form:
|
|
|
|
//
|
|
|
|
// private:
|
|
|
|
// acmecorp/db:
|
|
|
|
// ...
|
|
|
|
//
|
|
|
|
// In this example, "acmecorp/db" is the type and the name is shortened to just "db". Second, there is a longhand
|
|
|
|
// form for people who want more control over the naming of their services:
|
|
|
|
//
|
|
|
|
// private:
|
|
|
|
// customers:
|
|
|
|
// type: acmecorp/db
|
|
|
|
// ...
|
|
|
|
//
|
|
|
|
// In this example, "acmecorp/db" is still the type, however the name is given the nicer name of "customers."
|
Implement dependency versions
This change implements dependency versions, including semantic analysis, per the
checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0.
There's quite a bit in here but at a top-level this parses and validates dependency
references of the form
[[proto://]base.url]namespace/.../name[@version]
and verifies that the components are correct, as well as binding them to symbols.
These references can appear in two places at the moment:
* Service types.
* Cluster dependencies.
As part of this change, a number of supporting changes have been made:
* Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis.
This allows us to share logic around the validation of common AST types. This also
moves some of the logic around loading workspace.yaml files back to the parser, where
it can be unified with the way we load Mu.yaml files.
* New ast.Version and ast.VersionSpec types. The former represents a precise version
-- either a specific semantic version or a short or long Git SHA hash -- and the
latter represents a range -- either a Version, "latest", or a semantic range.
* New ast.Ref and ast.RefParts types. The former is an unparsed string that is
thought to contain a Ref, while the latter is a validated Ref that has been parsed
into its components (Proto, Base, Name, and Version).
* Added some type assertions to ensure certain structs implement certain interfaces,
to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.)
* Be consistent about prefixing error types with Error or Warning.
* Organize the core compiler driver's logic into three methods, FE, sema, and BE.
* A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
|
|
|
simplify := false
|
2016-11-16 22:11:58 +01:00
|
|
|
if svc.Type == "" {
|
Implement dependency versions
This change implements dependency versions, including semantic analysis, per the
checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0.
There's quite a bit in here but at a top-level this parses and validates dependency
references of the form
[[proto://]base.url]namespace/.../name[@version]
and verifies that the components are correct, as well as binding them to symbols.
These references can appear in two places at the moment:
* Service types.
* Cluster dependencies.
As part of this change, a number of supporting changes have been made:
* Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis.
This allows us to share logic around the validation of common AST types. This also
moves some of the logic around loading workspace.yaml files back to the parser, where
it can be unified with the way we load Mu.yaml files.
* New ast.Version and ast.VersionSpec types. The former represents a precise version
-- either a specific semantic version or a short or long Git SHA hash -- and the
latter represents a range -- either a Version, "latest", or a semantic range.
* New ast.Ref and ast.RefParts types. The former is an unparsed string that is
thought to contain a Ref, while the latter is a validated Ref that has been parsed
into its components (Proto, Base, Name, and Version).
* Added some type assertions to ensure certain structs implement certain interfaces,
to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.)
* Be consistent about prefixing error types with Error or Warning.
* Organize the core compiler driver's logic into three methods, FE, sema, and BE.
* A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
|
|
|
svc.Type = ast.Ref(svc.Name)
|
|
|
|
simplify = true
|
|
|
|
}
|
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
// Remember this service's type as a stack that must be bound later on.
|
|
|
|
ty, ok := p.registerDependencyToBind(pstack, svc.Type)
|
|
|
|
if !ok {
|
Implement dependency versions
This change implements dependency versions, including semantic analysis, per the
checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0.
There's quite a bit in here but at a top-level this parses and validates dependency
references of the form
[[proto://]base.url]namespace/.../name[@version]
and verifies that the components are correct, as well as binding them to symbols.
These references can appear in two places at the moment:
* Service types.
* Cluster dependencies.
As part of this change, a number of supporting changes have been made:
* Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis.
This allows us to share logic around the validation of common AST types. This also
moves some of the logic around loading workspace.yaml files back to the parser, where
it can be unified with the way we load Mu.yaml files.
* New ast.Version and ast.VersionSpec types. The former represents a precise version
-- either a specific semantic version or a short or long Git SHA hash -- and the
latter represents a range -- either a Version, "latest", or a semantic range.
* New ast.Ref and ast.RefParts types. The former is an unparsed string that is
thought to contain a Ref, while the latter is a validated Ref that has been parsed
into its components (Proto, Base, Name, and Version).
* Added some type assertions to ensure certain structs implement certain interfaces,
to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.)
* Be consistent about prefixing error types with Error or Warning.
* Organize the core compiler driver's logic into three methods, FE, sema, and BE.
* A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we used the simple form, we must now propagate the friendly name over to the service's name.
|
|
|
|
if simplify {
|
|
|
|
svc.Name = ty.Name.Simple()
|
2016-11-16 22:11:58 +01:00
|
|
|
}
|
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
// Add this service to the symbol table so that other service definitions can refer to it by name.
|
2016-11-16 22:11:58 +01:00
|
|
|
sym := NewServiceSymbol(svc.Name, svc)
|
|
|
|
if !p.b.RegisterSymbol(sym) {
|
2016-11-23 16:54:40 +01:00
|
|
|
p.Diag().Errorf(errors.ErrorSymbolAlreadyExists.At(pstack), sym.Name)
|
2016-11-16 22:11:58 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type binderPhase2 struct {
|
|
|
|
b *binder
|
2016-11-23 16:26:45 +01:00
|
|
|
top *ast.Stack
|
2016-11-16 22:11:58 +01:00
|
|
|
}
|
|
|
|
|
Implement dependency versions
This change implements dependency versions, including semantic analysis, per the
checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0.
There's quite a bit in here but at a top-level this parses and validates dependency
references of the form
[[proto://]base.url]namespace/.../name[@version]
and verifies that the components are correct, as well as binding them to symbols.
These references can appear in two places at the moment:
* Service types.
* Cluster dependencies.
As part of this change, a number of supporting changes have been made:
* Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis.
This allows us to share logic around the validation of common AST types. This also
moves some of the logic around loading workspace.yaml files back to the parser, where
it can be unified with the way we load Mu.yaml files.
* New ast.Version and ast.VersionSpec types. The former represents a precise version
-- either a specific semantic version or a short or long Git SHA hash -- and the
latter represents a range -- either a Version, "latest", or a semantic range.
* New ast.Ref and ast.RefParts types. The former is an unparsed string that is
thought to contain a Ref, while the latter is a validated Ref that has been parsed
into its components (Proto, Base, Name, and Version).
* Added some type assertions to ensure certain structs implement certain interfaces,
to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.)
* Be consistent about prefixing error types with Error or Warning.
* Organize the core compiler driver's logic into three methods, FE, sema, and BE.
* A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
|
|
|
var _ core.Visitor = &binderPhase2{} // compile-time assertion that the binder implements core.Visitor.
|
|
|
|
|
2016-11-16 22:11:58 +01:00
|
|
|
func (p *binderPhase2) Diag() diag.Sink {
|
|
|
|
return p.b.Diag()
|
2016-11-16 18:29:44 +01:00
|
|
|
}
|
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
func (p *binderPhase2) VisitWorkspace(workspace *ast.Workspace) {
|
Implement dependency versions
This change implements dependency versions, including semantic analysis, per the
checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0.
There's quite a bit in here but at a top-level this parses and validates dependency
references of the form
[[proto://]base.url]namespace/.../name[@version]
and verifies that the components are correct, as well as binding them to symbols.
These references can appear in two places at the moment:
* Service types.
* Cluster dependencies.
As part of this change, a number of supporting changes have been made:
* Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis.
This allows us to share logic around the validation of common AST types. This also
moves some of the logic around loading workspace.yaml files back to the parser, where
it can be unified with the way we load Mu.yaml files.
* New ast.Version and ast.VersionSpec types. The former represents a precise version
-- either a specific semantic version or a short or long Git SHA hash -- and the
latter represents a range -- either a Version, "latest", or a semantic range.
* New ast.Ref and ast.RefParts types. The former is an unparsed string that is
thought to contain a Ref, while the latter is a validated Ref that has been parsed
into its components (Proto, Base, Name, and Version).
* Added some type assertions to ensure certain structs implement certain interfaces,
to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.)
* Be consistent about prefixing error types with Error or Warning.
* Organize the core compiler driver's logic into three methods, FE, sema, and BE.
* A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
|
|
|
}
|
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
func (p *binderPhase2) VisitCluster(name string, cluster *ast.Cluster) {
|
Implement dependency versions
This change implements dependency versions, including semantic analysis, per the
checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0.
There's quite a bit in here but at a top-level this parses and validates dependency
references of the form
[[proto://]base.url]namespace/.../name[@version]
and verifies that the components are correct, as well as binding them to symbols.
These references can appear in two places at the moment:
* Service types.
* Cluster dependencies.
As part of this change, a number of supporting changes have been made:
* Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis.
This allows us to share logic around the validation of common AST types. This also
moves some of the logic around loading workspace.yaml files back to the parser, where
it can be unified with the way we load Mu.yaml files.
* New ast.Version and ast.VersionSpec types. The former represents a precise version
-- either a specific semantic version or a short or long Git SHA hash -- and the
latter represents a range -- either a Version, "latest", or a semantic range.
* New ast.Ref and ast.RefParts types. The former is an unparsed string that is
thought to contain a Ref, while the latter is a validated Ref that has been parsed
into its components (Proto, Base, Name, and Version).
* Added some type assertions to ensure certain structs implement certain interfaces,
to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.)
* Be consistent about prefixing error types with Error or Warning.
* Organize the core compiler driver's logic into three methods, FE, sema, and BE.
* A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
|
|
|
}
|
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
func (p *binderPhase2) VisitDependency(parent *ast.Workspace, ref ast.Ref, dep *ast.Dependency) {
|
Implement dependency versions
This change implements dependency versions, including semantic analysis, per the
checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0.
There's quite a bit in here but at a top-level this parses and validates dependency
references of the form
[[proto://]base.url]namespace/.../name[@version]
and verifies that the components are correct, as well as binding them to symbols.
These references can appear in two places at the moment:
* Service types.
* Cluster dependencies.
As part of this change, a number of supporting changes have been made:
* Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis.
This allows us to share logic around the validation of common AST types. This also
moves some of the logic around loading workspace.yaml files back to the parser, where
it can be unified with the way we load Mu.yaml files.
* New ast.Version and ast.VersionSpec types. The former represents a precise version
-- either a specific semantic version or a short or long Git SHA hash -- and the
latter represents a range -- either a Version, "latest", or a semantic range.
* New ast.Ref and ast.RefParts types. The former is an unparsed string that is
thought to contain a Ref, while the latter is a validated Ref that has been parsed
into its components (Proto, Base, Name, and Version).
* Added some type assertions to ensure certain structs implement certain interfaces,
to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.)
* Be consistent about prefixing error types with Error or Warning.
* Organize the core compiler driver's logic into three methods, FE, sema, and BE.
* A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
|
|
|
}
|
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
func (p *binderPhase2) VisitStack(stack *ast.Stack) {
|
|
|
|
// Populate the symbol table with this Stack's bound dependencies so that any type lookups are found.
|
|
|
|
util.Assert(stack.BoundDependencies != nil)
|
|
|
|
for _, nm := range ast.StableBoundDependencies(stack.BoundDependencies) {
|
2016-11-29 22:04:36 +01:00
|
|
|
var sym *Symbol
|
2016-11-23 16:26:45 +01:00
|
|
|
dep := stack.BoundDependencies[nm]
|
2016-11-29 22:04:36 +01:00
|
|
|
if dep.Stack == nil {
|
|
|
|
util.Assert(dep.Doc != nil)
|
|
|
|
sym = NewDocumentSymbol(dep.Ref.Name, dep.Doc)
|
|
|
|
} else {
|
|
|
|
sym = NewStackSymbol(dep.Ref.Name, dep.Stack)
|
|
|
|
}
|
2016-11-23 16:26:45 +01:00
|
|
|
if !p.b.RegisterSymbol(sym) {
|
2016-11-23 16:54:40 +01:00
|
|
|
p.Diag().Errorf(errors.ErrorSymbolAlreadyExists.At(stack), dep.Ref.Name)
|
2016-11-19 02:30:32 +01:00
|
|
|
}
|
|
|
|
}
|
2016-11-23 16:26:45 +01:00
|
|
|
|
|
|
|
// Ensure the name of the base is in scope, and remember the binding information.
|
|
|
|
if stack.Base != "" {
|
2016-11-25 21:58:29 +01:00
|
|
|
// TODO[marapongo/mu#7]: we need to plumb construction properties for this stack.
|
|
|
|
stack.BoundBase = p.ensureStackType(stack.Base, nil)
|
2016-11-19 02:30:32 +01:00
|
|
|
}
|
2016-11-16 20:09:45 +01:00
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
// Non-abstract Stacks must declare at least one Service.
|
|
|
|
if !stack.Predef && !stack.Abstract && len(stack.Services.Public) == 0 && len(stack.Services.Private) == 0 {
|
2016-11-23 16:54:40 +01:00
|
|
|
p.Diag().Errorf(errors.ErrorNonAbstractStacksMustDefineServices.At(stack))
|
2016-11-23 16:26:45 +01:00
|
|
|
}
|
2016-11-16 20:09:45 +01:00
|
|
|
}
|
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
func (p *binderPhase2) VisitProperty(parent *ast.Stack, name string, param *ast.Property) {
|
Implement dependency resolution
This change includes logic to resolve dependencies declared by stacks. The design
is described in https://github.com/marapongo/mu/blob/master/docs/deps.md.
In summary, each stack may declare dependencies, which are name/semver pairs. A
new structure has been introduced, ast.Ref, to distinguish between ast.Names and
dependency names. An ast.Ref includes a protocol, base part, and a name part (the
latter being an ast.Name); for example, in "https://hub.mu.com/mu/container/",
"https://" is the protocol, "hub.mu.com/" is the base, and "mu/container" is the
name. This is used to resolve URL-like names to package manager-like artifacts.
The dependency resolution phase happens after parsing, but before semantic analysis.
This is because dependencies are "source-like" in that we must load and parse all
dependency metadata files. We stick the full transitive closure of dependencies
into a map attached to the compiler to avoid loading dependencies multiple times.
Note that, although dependencies prohibit cycles, this forms a DAG, meaning multiple
inbound edges to a single stack may come from multiple places.
From there, we rely on ordinary visitation to deal with dependencies further.
This includes inserting symbol entries into the symbol table, mapping names to the
loaded stacks, during the first phase of binding so that they may be found
subsequently when typechecking during the second phase and beyond.
2016-11-21 20:19:25 +01:00
|
|
|
}
|
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
func (p *binderPhase2) VisitServices(parent *ast.Stack, svcs *ast.Services) {
|
2016-11-17 02:30:03 +01:00
|
|
|
}
|
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
func (p *binderPhase2) VisitService(pstack *ast.Stack, parent *ast.Services, name ast.Name, public bool,
|
Implement dependency versions
This change implements dependency versions, including semantic analysis, per the
checkin https://github.com/marapongo/mu/commit/83030685c3b8a3dbe96bd10ab055f029667a96b0.
There's quite a bit in here but at a top-level this parses and validates dependency
references of the form
[[proto://]base.url]namespace/.../name[@version]
and verifies that the components are correct, as well as binding them to symbols.
These references can appear in two places at the moment:
* Service types.
* Cluster dependencies.
As part of this change, a number of supporting changes have been made:
* Parse Workspaces using a full-blown parser, parser analysis, and semantic analysis.
This allows us to share logic around the validation of common AST types. This also
moves some of the logic around loading workspace.yaml files back to the parser, where
it can be unified with the way we load Mu.yaml files.
* New ast.Version and ast.VersionSpec types. The former represents a precise version
-- either a specific semantic version or a short or long Git SHA hash -- and the
latter represents a range -- either a Version, "latest", or a semantic range.
* New ast.Ref and ast.RefParts types. The former is an unparsed string that is
thought to contain a Ref, while the latter is a validated Ref that has been parsed
into its components (Proto, Base, Name, and Version).
* Added some type assertions to ensure certain structs implement certain interfaces,
to speed up finding errors. (And remove the coercions that zero-fill vtbl slots.)
* Be consistent about prefixing error types with Error or Warning.
* Organize the core compiler driver's logic into three methods, FE, sema, and BE.
* A bunch of tests for some of the above ... more to come in an upcoming change.
2016-11-23 01:58:23 +01:00
|
|
|
svc *ast.Service) {
|
2016-11-16 22:11:58 +01:00
|
|
|
// The service's type has been prepared in phase 1, and must now be bound to a symbol. All shorthand type
|
|
|
|
// expressions, intra stack references, cycles, and so forth, will have been taken care of by this earlier phase.
|
2016-11-20 01:13:13 +01:00
|
|
|
util.AssertMF(svc.Type != "",
|
|
|
|
"Expected all Services to have types in binding phase2; %v is missing one", svc.Name)
|
2016-11-25 21:58:29 +01:00
|
|
|
svc.BoundType = p.ensureStackType(svc.Type, svc.Props)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *binderPhase2) ensureStackType(ref ast.Ref, props ast.PropertyBag) *ast.Stack {
|
|
|
|
// There are two possibilities. The first is that a type resolves to an *ast.Stack. That's simple, we just fetch
|
|
|
|
// and return it. The second is that a type resolves to a *diag.Document. That's more complex, as we need to
|
|
|
|
// actually parse the stack from a document, supplying properties, etc., for template expansion.
|
|
|
|
nm := ref.MustParse().Name
|
|
|
|
|
|
|
|
_, stack := p.b.LookupStack(nm)
|
|
|
|
if stack != nil {
|
|
|
|
return stack
|
|
|
|
}
|
|
|
|
|
|
|
|
_, doc := p.b.LookupDocument(nm)
|
|
|
|
if doc != nil {
|
|
|
|
// If we got this far, we've loaded up the dependency's Mufile; parse it and return the result. Note that
|
|
|
|
// this will be processed later on in semantic analysis, to ensure semantic problems are caught.
|
|
|
|
p := NewParser(p.b.c)
|
|
|
|
stack := p.ParseStack(doc, props)
|
|
|
|
if p.Diag().Success() {
|
|
|
|
return stack
|
|
|
|
} else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
util.FailMF("Expected 1st pass of binding to guarantee type %v exists (%v)", ref, nm)
|
|
|
|
return nil
|
2016-11-16 20:09:45 +01:00
|
|
|
}
|