This change completes the implementation of dependency and type binding. The top-level change here is that, during the first semantic analysis AST walk, we gather up all unknown dependencies. Then the compiler resolves them, caching the lookups to ensure that we don't load the same stack twice. Finally, during the second and final semantic analysis AST walk, we populate the bound nodes by looking up what the compiler resolved for us.
107 lines
2.9 KiB
Go
107 lines
2.9 KiB
Go
// Copyright 2016 Marapongo, Inc. All rights reserved.
|
|
|
|
package ast
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/marapongo/mu/pkg/util"
|
|
)
|
|
|
|
// RefParts parses the parts of a Ref into a data structure for convenient access.
|
|
func (r Ref) Parse() (RefParts, error) {
|
|
s := string(r)
|
|
parsed := RefParts{}
|
|
|
|
// Look for the leading protocol, if any.
|
|
protoEnd := strings.Index(s, "://")
|
|
if protoEnd != -1 {
|
|
// Remember it and then strip it off for subsequent parsing.
|
|
parsed.Proto = s[:protoEnd+3]
|
|
s = s[protoEnd+3:]
|
|
}
|
|
|
|
// Strip off the version first, so looking for dots doesn't get confused.
|
|
verIndex := strings.Index(s, "@")
|
|
if verIndex != -1 {
|
|
parsed.Version = VersionSpec(s[verIndex+1:])
|
|
if err := parsed.Version.Check(); err != nil {
|
|
return parsed, errors.New("Illegal version spec: " + err.Error())
|
|
}
|
|
s = s[:verIndex]
|
|
}
|
|
|
|
// Now look to see if there is a dot, indicating a base part.
|
|
dotIndex := strings.Index(s, ".")
|
|
if dotIndex != -1 {
|
|
// A base exists; look for a slash (indicating the name), and capture everything up to it (including it).
|
|
// TODO(joe): this might be questionable; e.g., domain-less hosts will require a trailing period.
|
|
slashIndex := strings.Index(s, NameDelimiter)
|
|
if slashIndex == -1 {
|
|
return parsed, errors.New("Expected a name to follow the base URL")
|
|
} else {
|
|
parsed.Base = s[:slashIndex+1]
|
|
s = s[slashIndex+1:]
|
|
}
|
|
}
|
|
|
|
// Anything remaining at this point represents the name.
|
|
parsed.Name = Name(s)
|
|
|
|
return parsed, nil
|
|
}
|
|
|
|
// MustParse parses the parts of a Ref into a RefParts, failing fast if parsing fails.
|
|
func (r Ref) MustParse() RefParts {
|
|
p, err := r.Parse()
|
|
util.AssertMF(err == nil, "Expected a nil error from Ref.Parse; got %v", err)
|
|
return p
|
|
}
|
|
|
|
// RefParts represents a parsed Ref structure.
|
|
type RefParts struct {
|
|
Proto string // the protocol (e.g., "https://").
|
|
Base string // the base part of the URL (e.g., "mu.hub.com/").
|
|
Name Name // the name part of the URL (e.g., "mu/container").
|
|
Version VersionSpec // the version part of the URL (e.g., "^1.0.6").
|
|
}
|
|
|
|
var _ fmt.Stringer = RefParts{} // compile-time assertion that RefParts implements Stringer.
|
|
|
|
// DefaultRefProto is the default ref protocol.
|
|
const DefaultRefProto = "https://"
|
|
|
|
// DefaultRefBase is the base part used if a Ref doesn't specify one explicitly.
|
|
const DefaultRefBase = "hub.mu.com/"
|
|
|
|
// DefaultRefVersion is the default ref version if none is specified.
|
|
const DefaultRefVersion = LatestVersion
|
|
|
|
// Defaults replaces any empty parts of the RefParts with their default values.
|
|
func (r RefParts) Defaults() RefParts {
|
|
d := r
|
|
if d.Proto == "" {
|
|
d.Proto = DefaultRefProto
|
|
}
|
|
if d.Base == "" {
|
|
d.Base = DefaultRefBase
|
|
}
|
|
if string(d.Version) == "" {
|
|
d.Version = DefaultRefVersion
|
|
}
|
|
return d
|
|
}
|
|
|
|
func (r RefParts) Ref() Ref {
|
|
return Ref(r.String())
|
|
}
|
|
|
|
func (r RefParts) String() string {
|
|
s := r.Proto + r.Base + string(r.Name)
|
|
if string(r.Version) != "" {
|
|
s += "@" + string(r.Version)
|
|
}
|
|
return s
|
|
}
|