2016-11-23 16:26:45 +01:00
|
|
|
// 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/diag"
|
|
|
|
"github.com/marapongo/mu/pkg/errors"
|
|
|
|
"github.com/marapongo/mu/pkg/util"
|
|
|
|
"github.com/marapongo/mu/pkg/workspace"
|
|
|
|
)
|
|
|
|
|
|
|
|
// buildDocumentSema runs the middle semantic analysis phases of the compiler.
|
|
|
|
func (c *compiler) buildDocumentSema(w workspace.W, stack *ast.Stack) {
|
|
|
|
// Perform semantic analysis on all stacks passes to validate, transform, and/or update the AST.
|
|
|
|
b := NewBinder(c)
|
|
|
|
c.bindStack(b, w, stack)
|
|
|
|
if !c.Diag().Success() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// bindStack performs the two phases of binding plus dependency resolution for the given Stack.
|
|
|
|
func (c *compiler) bindStack(b Binder, w workspace.W, stack *ast.Stack) {
|
2016-12-01 20:14:05 +01:00
|
|
|
util.Assert(stack != nil)
|
|
|
|
|
2016-11-23 16:26:45 +01:00
|
|
|
// First prepare the AST for binding.
|
Bind properties that refer to types
A stack property can refer to other stack types. For example:
properties:
gateway:
type: aws/ec2/internetGateway
...
In such cases, we need to validate the property during binding,
in addition to binding it to an actual type so that we can later
validate callers who are constructing instances of this stack
and providing property values that we must typecheck.
Note that this binding is subtly different than existing stack
type binding. All the name validation, resolution, and so forth
are the same. However, notice that in this case we are not actually
supplying any property setters. That is, internetGateway is not
an "expanded" type, in that we have not processed any of its templates.
An analogy might help: this is sort of akin referring to an
uninstantiated generic type in a traditional programming language,
versus its instantiated form. In this case, certain properties aren't
available to us, however we can still use it for type identity, etc.
2016-12-03 20:14:06 +01:00
|
|
|
refs := b.PrepareStack(stack)
|
2016-11-23 16:26:45 +01:00
|
|
|
if !c.Diag().Success() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next, resolve all dependencies discovered during this first pass.
|
Bind properties that refer to types
A stack property can refer to other stack types. For example:
properties:
gateway:
type: aws/ec2/internetGateway
...
In such cases, we need to validate the property during binding,
in addition to binding it to an actual type so that we can later
validate callers who are constructing instances of this stack
and providing property values that we must typecheck.
Note that this binding is subtly different than existing stack
type binding. All the name validation, resolution, and so forth
are the same. However, notice that in this case we are not actually
supplying any property setters. That is, internetGateway is not
an "expanded" type, in that we have not processed any of its templates.
An analogy might help: this is sort of akin referring to an
uninstantiated generic type in a traditional programming language,
versus its instantiated form. In this case, certain properties aren't
available to us, however we can still use it for type identity, etc.
2016-12-03 20:14:06 +01:00
|
|
|
deprefs := make(ast.DependencyRefs)
|
|
|
|
for _, ref := range refs {
|
2016-12-02 00:39:58 +01:00
|
|
|
// Only resolve dependencies that are currently unknown. This will exlude built-in types that have already
|
|
|
|
// been bound to a stack during the first phase of binding. Note that we don't actually parse and perform
|
|
|
|
// template substitution here; instead, we remember the document and let the binder do this, since it has
|
|
|
|
// all of the information necessary to create a unique Stack per-PropertyBag used to instantiate it.
|
|
|
|
if doc := c.resolveDependency(w, stack, ref); doc != nil {
|
Custom types, round 1
This change overhauls the core of how types are used by the entire
compiler. In particular, we now have an ast.Type, and have begun
using its use where appropriate. An ast.Type is a union representing
precisely one of the possible sources of types in the system:
* Primitive type: any, bool, number, string, or service.
* Stack type: a resolved reference to an actual concrete stack.
* Schema type: a resolved reference to an actual concrete schema.
* Unresolved reference: a textual reference that hasn't yet been
resolved to a concrete artifact.
* Uninstantiated reference: a reference that has been resolved to
an uninstantiated stack, but hasn't been bound to a concrete
result yet. Right now, this can point to a stack, however
eventually we would imagine this supporting inter-stack schema
references also.
* Decorated type: either an array or a map; in the array case, there
is a single inner element type; in the map case, there are two,
the keys and values; in all cases, the type recurses to any of the
possibilities listed here.
All of the relevant AST nodes have been overhauled accordingly.
In addition to this, we now have an ast.Schema type. It is loosely
modeled on JSON Schema in its capabilities (http://json-schema.org/).
Although we parse and perform some visitation and binding of these,
there are mostly placeholders left in the code for the interesting
aspects, such as registering symbols, resolving dependencies, and
typechecking usage of schema types.
This is part of the ongoing work behind marapongo/mu#9.
2016-12-06 23:49:47 +01:00
|
|
|
deprefs[ref] = &ast.UninstStack{Ref: ref, Doc: doc}
|
2016-11-23 16:26:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if !c.Diag().Success() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-12-01 20:14:05 +01:00
|
|
|
// Complete the binding process.
|
Bind properties that refer to types
A stack property can refer to other stack types. For example:
properties:
gateway:
type: aws/ec2/internetGateway
...
In such cases, we need to validate the property during binding,
in addition to binding it to an actual type so that we can later
validate callers who are constructing instances of this stack
and providing property values that we must typecheck.
Note that this binding is subtly different than existing stack
type binding. All the name validation, resolution, and so forth
are the same. However, notice that in this case we are not actually
supplying any property setters. That is, internetGateway is not
an "expanded" type, in that we have not processed any of its templates.
An analogy might help: this is sort of akin referring to an
uninstantiated generic type in a traditional programming language,
versus its instantiated form. In this case, certain properties aren't
available to us, however we can still use it for type identity, etc.
2016-12-03 20:14:06 +01:00
|
|
|
deps := b.BindStack(stack, deprefs)
|
2016-12-02 00:39:58 +01:00
|
|
|
if !c.Diag().Success() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now ensure we bind all dependency stacks too.
|
|
|
|
for _, dep := range deps {
|
|
|
|
c.bindStack(b, w, dep)
|
|
|
|
}
|
2016-12-02 22:23:18 +01:00
|
|
|
if !c.Diag().Success() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finally, now that we know the entire tree, and its transitive closure, is bound, perform final validation.
|
|
|
|
b.ValidateStack(stack)
|
2016-11-23 16:26:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// resolveDependency loads up the target dependency from the current workspace using the stack resolution rules.
|
2016-12-02 00:39:58 +01:00
|
|
|
func (c *compiler) resolveDependency(w workspace.W, stack *ast.Stack, ref ast.Ref) *diag.Document {
|
|
|
|
glog.V(3).Infof("Loading Stack %v dependency %v", stack.Name, ref)
|
2016-11-23 16:26:45 +01:00
|
|
|
|
|
|
|
// First, see if we've already loaded this dependency (anywhere in any Stacks). If yes, reuse it.
|
|
|
|
// TODO: check for version mismatches.
|
2016-11-25 21:58:29 +01:00
|
|
|
if doc, exists := c.deps[ref]; exists {
|
|
|
|
return doc
|
2016-11-23 16:26:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// There are many places a dependency could come from. Consult the workspace for a list of those paths. It will
|
|
|
|
// return a number of them, in preferred order, and we simply probe each one until we find something.
|
2016-12-02 00:39:58 +01:00
|
|
|
dep := ref.MustParse()
|
2016-11-23 16:26:45 +01:00
|
|
|
for _, loc := range w.DepCandidates(dep) {
|
|
|
|
// Try to read this location as a document.
|
|
|
|
isMufile := workspace.IsMufile(loc, c.Diag())
|
|
|
|
glog.V(5).Infof("Probing for dependency %v at %v: %v", dep, loc, isMufile)
|
|
|
|
|
|
|
|
if isMufile {
|
|
|
|
doc, err := diag.ReadDocument(loc)
|
|
|
|
if err != nil {
|
2016-11-23 16:44:03 +01:00
|
|
|
c.Diag().Errorf(errors.ErrorCouldNotReadMufile.AtFile(loc), err)
|
2016-11-23 16:26:45 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Memoize this in the compiler's cache and return it.
|
2016-11-25 21:58:29 +01:00
|
|
|
c.deps[ref] = doc
|
|
|
|
return doc
|
2016-11-23 16:26:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we got to this spot, we could not find the dependency. Issue an error and bail out.
|
2016-12-02 00:39:58 +01:00
|
|
|
c.Diag().Errorf(errors.ErrorStackTypeNotFound.At(stack), ref)
|
2016-11-23 16:26:45 +01:00
|
|
|
return nil
|
|
|
|
}
|