Commit graph

173 commits

Author SHA1 Message Date
joeduffy 2849a3e64b Print descriptions as header comments 2017-01-16 14:45:32 -08:00
joeduffy 2adb334e6a Fix a bogus decoding case
The ObjectLiteral case should obviously call the decodeObjectLiteral
function, and not decodeArrayLiteral.
2017-01-16 12:27:30 -08:00
joeduffy 57d5538ec1 Fix IfStatement json metadata
The node name is "condition", not "expression".
2017-01-16 12:27:04 -08:00
joeduffy a2d847f1ef Use stable map enumeration
This change uses stable map enumeration so that output doesn't
change randomly based on hashing.
2017-01-16 12:02:33 -08:00
joeduffy 2ee3671c36 Progress on the mu describe command
This change makes considerable progress on the `mu describe` command;
the only thing remaining to be implemented now is full IL printing.  It
now prints the full package/module structure.

For example, to print the set of exports from our scenarios/point test:

    $ mujs tools/mujs/tests/output/scenarios/point/ | mu describe - -e
    package "scenarios/point" {
	    dependencies []
	    module "index" {
		    class "Point" [public] {
			    method "add": (other: any): any
			    property "x" [public, readonly]: number
			    property "y" [public, readonly]: number
			    method ".ctor": (x: number, y: number): any
		    }
	    }
    }

This is just a pretty-printed, but is coming in handy with debugging.
2017-01-16 11:47:21 -08:00
joeduffy 3b0184cec3 Add a missing JSON annotation
The NewExpression AST node type was missing a JSON annotation on
its Type field, leading to decoding errors.

Now, with this, the full suite of MuJS test cases can be unmarshaled
into fully populated MuPack and MuIL structures.
2017-01-16 10:04:25 -08:00
joeduffy 1948380eb2 Add custom decoders to eliminate boilerplate
This change overhauls the approach to custom decoding.  Instead of decoding
the parts of the struct that are "trivial" in one pass, and then patching up
the structure afterwards with custom decoding, the decoder itself understands
the notion of custom decoder functions.

First, the general purpose logic has moved out of pkg/pack/encoding and into
a new package, pkg/util/mapper.  Most functions are now members of a new top-
level type, Mapper, which may be initialized with custom decoders.  This
is a map from target type to a function that can decode objects into it.

Second, the AST-specific decoding logic is rewritten to use it.  All AST nodes
are now supported, including definitions, statements, and expressions.  The
overall approach here is to simply define a custom decoder for any interface
type that will occur in a node field position.  The mapper, upon encountering
such a type, will consult the custom decoder map; if a decoder is found, it
will be used, otherwise an error results.  This decoder then needs to switch
on the type discriminated kind field that is present in the metadata, creating
a concrete struct of the right type, and then converting it to the desired
interface type.  Note that, subtly, interface types used only for "marker"
purposes don't require any custom decoding, because they do not appear in
field positions and therefore won't be encountered during the decoding process.
2017-01-16 09:41:26 -08:00
joeduffy 9939024ccd Begin decoding statements 2017-01-16 07:47:41 -08:00
joeduffy 3ef55ac6b3 Add AST type assertions and kind constants 2017-01-16 07:47:12 -08:00
joeduffy 99f60bf5c8 Clean up some decoding logic
This change splits up the decoding logic into multiple files, to
mirror the AST package structure that the functions correspond to.

Additionally, there is now less "loose" reflection and dynamic lookup
code scattered throughout; it is now consolidated into the decoder,
with a set of "generic" functions like `fieldObject`, `asString`, etc.
2017-01-16 06:46:56 -08:00
joeduffy fee6d94e1f Implement class member decoding
This change implements custom class member decoding.  As with module methods,
the function body AST nodes remain nil, as custom AST decoding isn't yet done.
2017-01-16 06:10:24 -08:00
joeduffy 14c040bc7f Implement custom decoding of ModuleMembers
This change begins to implement some of the AST custom decoding, beneath
the Package's Module map.  In particular, we now unmarshal "one level"
beyond this, populating each Module's ModuleMember map.  This includes
Classes, Exports, ModuleProperties, and ModuleMethods.  The Class AST's
Members have been marked "custom", in addition to Block's Statements,
because they required kind-directed decoding.  But Exports and
ModuleProperties can be decoded entirely using the tag-directed decoding
scheme.  Up next, custom decoding of ClassMembers.  At that point, all
definition-level decoding will be done, leaving MuIL's ASTs.
2017-01-15 14:57:42 -08:00
joeduffy 5f33292496 Move assertion/failure functions
This change just moves the assertion/failure functions from the pkg/util
package to pkg/util/contract, so things read a bit nicer (i.e.,
`contract.Assert(x)` versus `util.Assert(x)`).
2017-01-15 14:26:48 -08:00
joeduffy f0b9f157de Add tests for decoding more complex nested structures
This adds deeper nesting into our tests, plus a few more examples of
pointer-based data structures inside of those deeply nested structures.
2017-01-15 13:50:00 -08:00
joeduffy 1ad99bcea1 Fix a few AST marshaling things
This fixes a few things so that MuPackages now unmarshal:

* Mark Module.Members as requiring "custom" decoding.  This is required
  because that's the first point in the tree that leverages polymorphism.
  Everything "above" this unmarshals just fine (e.g., package+modules).

* As such, stop marking Package.Modules as "custom".

* Add the Kind field to the Node.  Although we won't use this for type
  discrimination in the same way, since Go gives us RTTI on the structs,
  it is required for unmarshaling (to avoid "unrecognized fields" errors)
  and it's probably handy to have around for logging, messages, etc.

* Mark Position.Line and Column with "json" annotations so that they
  unmarshal correctly.
2017-01-14 11:22:54 -08:00
joeduffy bd32867e81 Adjust pointers
This change adjusts pointers correctly when unmarshaling into target
pointer types.  This handles arrays and maps of pointer elements, in
addition to consolidating existing logic for marshaling into a
destination top-level pointer as well.
2017-01-14 11:21:27 -08:00
joeduffy d1188ed8c8 Add recursive struct unmarshaling 2017-01-14 10:06:55 -08:00
joeduffy c0c75d0f08 Split decoder logic out from MuPack-specific decode functions 2017-01-14 09:50:25 -08:00
joeduffy ab2d0ae6cb Implement tag-directed decoding
This change eliminates boilerplate decoding logic in all the different
data structures, and instead uses a new tag-directed decoding scheme.
This works a lot like the JSON deserializers, in that it recognizes the
`json:"name"` tags, except that we permit annotation of fields that
require custom deserialization, as `json:"name,custom"`.  The existing
`json:"name,omitempty"` tag is recognized for optional fields.
2017-01-14 09:42:05 -08:00
joeduffy 120f139812 Decode dependencies metadata 2017-01-14 07:58:21 -08:00
joeduffy d334ea322b Add custom decoding for MuPack metadata
This adds basic custom decoding for the MuPack metadata section of
the incoming JSON/YAML.  Because of the type discriminated union nature
of the incoming payload, we cannot rely on the simple built-in JSON/YAML
unmarshaling behavior.  Note that for the metadata section -- what is
in this checkin -- we could have, but the IL AST nodes are problematic.
(To know what kind of structure to creat requires inspecting the "kind"
field of the IL.)  We will use a reflection-driven walk of the target
structure plus a weakly typed deserialized map[string]interface{}, as
is fairly customary in Go for scenarios like this (though good libaries
seem to be lacking in this area...).
2017-01-14 07:40:13 -08:00
joeduffy e7dbfa59c3 Create MuPack and MuIL packages in our Go toolset
This change carries over all of the metadata shapes in the MuPack
and MuIL file formats to our Go toolset.  This includes creating a
proper discriminated AST type tree along with correct annotations
so that the metadata will serialize and deserialize correctly.
2017-01-13 14:32:10 -08:00
joeduffy d22e6b44b5 Add orElse/orEmpty template functions
This change adds the ability to substitute a default value if one is
missing from a map (orElse), and/or to substitute an empty string (orEmpty).
2016-12-09 17:22:09 -08:00
joeduffy db80229899 Fix a few type binding mistakes
* Persue the default/optional checking if a property value == nil.

* Use the Interface() function to convert a reflect.Type to its underlying
  interface{} value.  This is required for typechecking to check out.

* Also, unrelated to the above, change type assertions to use nil rather than
  allocating real objects.  Although minimal, this incurs less GC pressure.
2016-12-09 13:12:57 -08:00
joeduffy b408c3ce2a Pass compiler options to template evaluation
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}}
2016-12-09 12:42:28 -08:00
joeduffy 3c5ca84d89 Switch back to the official YAML repo
Sam merged the pull request, so we can go back to the official repo.
This closes https://github.com/marapongo/mu/issues/28.
2016-12-09 11:59:05 -08:00
joeduffy e137837455 Revert back to T[], instead of []T, for array syntax
This change reverts the syntax for arrays back to T[] from []T.  The main
reason is that YAML doesn't permit unquoted strings beginning with [], meaning
any array type needs to be quoted as in "[]T", which is annoying compared to all
other primitive types which don't require quotes.  And, anyway, this syntax is
more familiar too.

I've also added a number of tests.
2016-12-07 13:24:05 -08:00
joeduffy 6a8df6126a Reject Refs with missing names 2016-12-07 13:07:54 -08:00
joeduffy eb6ef5a1b8 Fix control paths within bindValue
Any of the bindXValue routines can fail if there was no way to convert
the interface{} to an ast.Literal.  In such a case, we need to issue an
error about the wrong type being passed.  Unfortunately, in the most
recent set of changes, we began simply returning nils without issuing
the error.  This change fixes that.
2016-12-07 12:40:26 -08:00
joeduffy 9e005fb6d8 Rename Schemas to Types
This change renames Schemas to Types on Stack.  More interestingly, it
renames the JSON/YAML property used to specify them, from "schemas:" to
"types:"; I feel like this reads more naturally, especially as a sister
to the existing "services:" section.
2016-12-06 20:56:47 -08:00
joeduffy 86219e781b Custom types, round 2
This checkin continues progress on marapongo/mu#9.  It's still not
complete, however we're getting there.  In particular, this includes:

* Rename of ComplexLiteral to SchemaLiteral, as it is used exclusively
  for schematized types.  Also includes a set of changes associated
  with this, like deep value conversion to `map[string]interface{}`.

* Binding of schema types included within a Stack.  This allows names in
  type references to be bound to those schema types during typechecking.
  This also includes binding schema properties, reusing all the existing
  property binding logic for stacks.  In this way, properties between
  stacks and custom schema types are one and the same, which is nice.

* Enforcement for custom schema constraints; this includes Pattern,
  MaxLength, MinLength, Maximum, and Minimum, as per the JSON Schema
  specification.
2016-12-06 20:51:05 -08:00
joeduffy 713fe29fef 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 14:49:47 -08:00
joeduffy a28f02ce68 Use intrinsics in place of predefineds
This change leverages intrinsics in place of the predefined types.
It remains to be seen if we can reach 100% on this, however I am hopeful.
It's also nice that the system will be built "out of itself" with this
approach; in other words, each of the types is simply a Mufile that can
use conditional targeting as appropriate for the given cloud providers.
If we find that this isn't enough, we can always bring back the concept.
2016-12-05 16:13:49 -08:00
joeduffy ae9ffd0a0c Prohibit instantiation of abstract stacks 2016-12-05 15:53:36 -08:00
joeduffy de58637162 Use the long-hand CF Ref syntax
This change stops using the short-hand "!Ref" YAML syntax.  The Golang
marshaler encodes it with quotes and, apparently, has no way to suppress
this behavior; this isn't surprising, since the YAML parser we're using
admits it doesn't support this aspect of the YAML spec fully.  But that's
okay, the long-hand syntax works just fine, and has the added benefit
that we don't need to special case the logig for JSON versus YAML.
2016-12-05 15:20:11 -08:00
joeduffy 069342fef6 Issue an error for missing property names 2016-12-05 15:18:04 -08:00
joeduffy a82a49291b Simplify mu/x/cf property mapping
I think things have gotten a little out of hand with the way mu/x/cf
auto-maps properties.  In the beginning, it looked like everything could
be trivially auto-mapped, and I wanted to avoid the verbosity of mapping
each property by hand (since you can easily fat finger a name, mess up
capitalization, forget one, etc).  But then we began mapping service
references using proper CloudFormation !Refs, which meant suppressing
some of the auto-mappings, etc., etc.  This led to properties, extraProperties,
skipProperties, renamedProperties, and so on... Pretty confusing IMHO.

I just took a step back and decided to eliminate auto-mapping.  Instead,
you get two options: properties just lists a set of property name mappings,
and extraProperties lets you do template magic to map thing instead if you
wish to take matters into your own hands.  The result isn't too verbose
and has a lot less magic going on so it's easier to understand.
2016-12-05 15:04:33 -08:00
joeduffy 73a3699ea0 Add a renamedProperties section to aws/x/cf
This enables properties to be mapped to arbitrary names, as is needed
to translate strongly typed capability references into string CF IDs.
2016-12-05 14:25:23 -08:00
joeduffy 5b791aab77 Introduce intrinsic types
This change eliminates the special type mu/extension in favor of extensible
intrinsic types.  This subsumes the previous functionality while also fixing
a number of warts with the old model.

In particular, the old mu/extension approach deferred property binding until
very late in the compiler.  In fact, too late.  The backend provider for an
extension simply received an untyped bag of stuff, which it then had to
deal with.  Unfortunately, some operations in the binder are inaccessible
at this point because doing so would cause a cycle.  Furthermore, some
pertinent information is gone at this point, like the scopes and symtables.

The canonical example where we need this is binding services names to the
services themselves; e.g., the AWS CloudFormation "DependsOn" property should
resolve to the actual service names, not the string values.  In the limit,
this requires full binding information.

There were a few solutions I considered, including ones that've required
less code motion, however this one feels the most elegant.

Now we permit types to be marked as "intrinsic."  Binding to these names
is done exactly as ordinary name binding, unlike the special mu/extension
provider name.  In fact, just about everything except code-generation for
these types is the same as ordinary types.  This is perfect for the use case
at hand, which is binding properties.

After this change, for example, "DependsOn" is expanded to real service
names precisely as we need.

As part of this change, I added support for three new basic schema types:

* ast.StringList ("string[]"): a list of strings.
* ast.StringMap ("map[string]any"): a map of strings to anys.
* ast.ServiceList ("service[]"): a list of service references.

Obviously we need to revisit this and add a more complete set.  This work
is already tracked by marapongo/mu#9.

At the end of the day, it's likely I will replace all hard-coded predefined
types with intrinsic types, for similar reasons to the above.
2016-12-05 13:46:18 -08:00
joeduffy 06625e9541 Dig through to the "real" AWS resources for !Refs 2016-12-05 10:46:18 -08:00
joeduffy a6986be090 Auto-map from the stack's bound properties 2016-12-05 10:30:38 -08:00
joeduffy db4a87d7c4 Don't rebind properties
There are two cases to consider in the case of a stack's bound properties.
First, it was a stack we didn't need to construct.  This is the case for
built-in primitives.  In that case, we must rebind each service uniquely.
Second, it was an imported stack, which by definition we had to construct.
In this case, we don't need to rebind the properties -- we already did so
-- and can just reuse them in the service's own bound properties.

I am pondering a better way of representing this and will probably do this
soon.  The concept of unconstructed vs. constructed types should be unified
between the built-in types and imported ones.  (Kind of like generics.)
But, I still need a little bit more time prototyping before I make up my
mind what a better way to represent all of this might look like.
2016-12-05 10:25:56 -08:00
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
joeduffy 94cd53f2dd Fix a reference to Props that should be BoundProps 2016-12-03 15:23:28 -08:00
joeduffy f726c21402 Remember parent documents during template expansion
During subtypeOf checking, we need to walk the chain of documents from
which a stack came.  This is because, due to template expansion, we'll
end up with a different document for instantiated types than uninstantiated
ones.  This change keeps track of the parent and walks it appropriately.
2016-12-03 15:19:45 -08:00
joeduffy 17f53f419f Don't force selection
The prior change wasn't quite right vis-a-vis service selection.  By default,
we actually want to pick the service itself that was named by a capability reference.
We only want to pick one of its public exported services as the selected one when
a selector is given.  For convenience, we still have "<service>:." to pick the sole
public service, however, no longer is a selector absolutely required.
2016-12-03 14:46:19 -08:00
joeduffy 412a54e5a7 Switch to joeduffy/yaml
See https://github.com/marapongo/mu/issues/28 for details.
2016-12-03 13:18:08 -08:00
joeduffy e3a2002155 Support binding to arbitrary service types
This implements support for arbitrary service types on properties,
not just the weakly typed "service".  For example, in the AWS stacks,
the aws/ec2/route type requires a routeTable, among other things:

        name: aws/ec2/route
        properties:
                routeTable:
                        type: aws/ec2/routeTable

This not only binds the definition of such properties, but also the
callsites of those creating stacks and supplying values for them.
This includes checking for concrete, instantiated, and even base
types, so that, for instance, if a custom stack derived from
aws/ec2/routeTable using the base property, in the above example
it could be supplied as a legal value for the routeTable property.
2016-12-03 13:00:08 -08:00
joeduffy 9181a69033 Store deps to be bound in an array
The previous code stored dependencies in a map.  This caused non-determinism
in the order in which the resulting dependencies would be bound.  Instead of
doing that, this change tracks them in an array, simply using a map to avoid
binding duplicate dependencies.
2016-12-03 11:30:15 -08:00
joeduffy d63a09ea2f 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 11:14:06 -08:00