Commit graph

951 commits

Author SHA1 Message Date
joeduffy 089a179553 Mark class __init__ methods as ctors 2017-04-12 10:28:57 -07:00
joeduffy aeb81008bf Implement classes and binary operator expressions 2017-04-12 08:46:43 -07:00
joeduffy 22bfb34d53 Implement CocoPy augmented assign operator 2017-04-11 12:54:08 -07:00
joeduffy aa730b5913 Translate CocoPy subscripts
This change implements simple index-based CocoPy subscripts (and
not the more fully featured slicing ones).

Alongside this, we relax a binder-time check that all dynamic
access types must be strings.  The eval code already handles
numeric (array) accesses, so we will permit these to flow through.
2017-04-11 12:33:30 -07:00
joeduffy 9adfa6a18f Relax calling assertions
This permits non-nil `this` objects for dynamically loaded properties
of classes and modules, provided they are the prototype or module
object for the target function, respectively.
2017-04-11 11:53:06 -07:00
joeduffy ead6a107ee Implement record types and primary properties
This change emits all CocoJS interfaces as records.  This allows us to
safely construct instances of them from Python using anonymous properties,
essentially emulating object literals.

For example, given a CocoJs interface defined as such:

    interface Foo {
        x;
        y?;
        z;
    }

we can easily construct fresh instances using normal JS literals:

    let f = { x: 42, z: "bar" };

But, Python doesn't have the equivalent literal syntax, wedging us.
It's now possible to initialize an instance from CocoPy as follows:

    let f = Foo(x=42, z="bar")

This leverages the notion of records and primary properties, as
described in our CocoPack/CocoIL design documents.
2017-04-11 11:37:24 -07:00
joeduffy d58ff56363 Rename some out of date work item references 2017-04-11 10:19:34 -07:00
joeduffy 1e5bf7e5bb Fix three evaluation bugs
* Pushing a class scope should permit subclasses (assertion).

* Returning nothing is legal for voids *and* dynamic (the latter was missing).

* The dynamic load readonly lval check is redundant; we check at the site of
  assignment and doing it here as well led to two errors per usage.
2017-04-11 09:10:22 -07:00
joeduffy 1299e4ade7 Require blocks in fewer places
Due to Python's ... interesting ... scoping rules, we want to avoid
forcing block scopes in certain places.  Instead, we will let arbitrary
statements take their place.  Of course, this could be a block, but it
very well could be a multi-statement (essentially a block that doesn't
imply a new lexical scope), or anything, really.
2017-04-10 10:06:27 -07:00
joeduffy 2c6ad1e331 Fix a handful of things
* Move checking the validity of a derived class with no constructor
  from evlauation to binding (verification).  Furthermore, make it
  a verification error instead of an assert, and make the checking
  complete (i.e., don't just check for the existence of a base class,
  also check for the existence of a ctor, recursively).

* Refactor the constructor accessor out of the evaluator and make it
  a method, Ctor, on the Function abstraction.

* Add a recursive Module population case to property initialization.

* Only treat the top-most frame in a module's initializer as belonging
  to the module scope.  This avoids interpreting block scopes within
  that initializer as belonging to the module scope.  Ultimately, it's
  up to the language compiler to decide where to place scopes, but this
  gives it more precise control over module scoping.

* Assert against unsupported named/star/keyword args in CocoPy.
2017-04-10 08:36:48 -07:00
joeduffy 346bcca77c Permit prototype invocation as "new"
If we encounter a dynamic invocation of a prototype object, we will
interpret it as an object allocation.  This corresponds to code like

    import ec2 from aws
    instance = ec2.Instance(...)

where the second line dynamically loads the prototype object for the
Instance class from the module object for the aws/ec2 module, and
invokes it.
2017-04-09 09:29:58 -07:00
joeduffy 3cb734cc98 Populate module objects with exports 2017-04-09 09:21:23 -07:00
joeduffy 3579bf7977 Implement "from x import y, z"-style imports 2017-04-09 08:57:47 -07:00
joeduffy 3ef977e19c Support named imports
This change adds support for naming imports.  At the moment, this simply
makes the names dynamically accessible for languages that do dynamic loads
against module objects, versus strongly typed tokens.  The basic scheme
is to keep two objects per module: one that contains the globals and its
prototype parent that contains just the exports.  This ensures we can
share the same slots while attaining the desired information hiding
(e.g., when handing out an object for dynamic access, we give out this
parent objects, while when loading globals from within a module, we use
the childmost one that contains all private and exported variables).
2017-04-09 08:44:58 -07:00
joeduffy e96d4018ae Switch to imports as statements
The old model for imports was to use top-level declarations on the
enclosing module itself.  This was a laudible attempt to simplify
matters, but just doesn't work.

For one, the order of initialization doesn't precisely correspond
to the imports as they appear in the source code.  This could incur
some weird module initialization problems that lead to differing
behavior between a language and its Coconut variant.

But more pressing as we work on CocoPy support, it doesn't give
us an opportunity to dynamically bind names in a correct way.  For
example, "import aws" now needs to actually translate into a variable
declaration and assignment of sorts.  Furthermore, that variable name
should be visible in the environment block in which it occurs.

This change switches imports to act like statements.  For the most
part this doesn't change much compared to the old model.  The common
pattern of declaring imports at the top of a file will translate to
the imports happening at the top of the module's initializer.  This
has the effect of initializing the transitive closure just as it
happened previously.  But it enables alternative models, like imports
inside of functions, and -- per the above -- dynamic name binding.
2017-04-08 18:16:10 -07:00
joeduffy 45a16b725f Use the correct module name on the AST output 2017-04-08 17:31:12 -07:00
joeduffy 3d96cf494d Store parsed function bodes on the ASTs 2017-04-08 17:05:42 -07:00
joeduffy 54e89ad608 Permit localvar lookups that come up empty-handed
Previously, it was an error to look up a local that didn't exist.
Now it is common, thanks to dynamic lookups.  A few code-paths didn't
previously handle this adequately; now they do.
2017-04-08 17:04:43 -07:00
joeduffy f773000ef9 Implement dynamic loads from the environment¬
This rearranges the way dynamic loads work a bit.  Previously, they¬
required an object, and did a dynamic lookup in the object's property¬
map.  For real dynamic loads -- of the kind Python uses, obviously,¬
but also ECMAScript -- we need to search the "environment".

This change searches the environment by looking first in the lexical¬
scope in the current function.  If a variable exists, we will use it.¬
If that misses, we then look in the module scope.  If a variable exists¬
there, we will use it.  Otherwise, if the variable is used in a non-lval
position, an dynamic error will be raised ("name not declared").  If
an lval, however, we will lazily allocate a slot for it.

Note that Python doesn't use block scoping in the same way that most
languages do.  This behavior is simply achieved by Python not emitting
any lexically scoped blocks other than at the function level.

This doesn't perfectly achieve the scoping behavior, because we don't
yet bind every name in a way that they can be dynamically discovered.
The two obvious cases are class names and import names.  Those will be
covered in a subsequent commit.

Also note that we are getting lucky here that class static/instance
variables aren't accessible in Python or ECMAScript "ambiently" like
they are in some languages (e.g., C#, Java); as a result, we don't need
to introduce a class scope in the dynamic lookup.  Some day, when we
want to support such languages, we'll need to think about how to let
languages control the environment probe order; for instance, perhaps
the LoadDynamicExpression node can have an "environment" property.
2017-04-08 16:47:15 -07:00
joeduffy 9c1ea1f161 Fix some poor hygiene
A few linty things crept in; this addresses them.
2017-04-08 07:44:02 -07:00
joeduffy 843787f266 Emit more dynamic loads
This changes the CocoPy default load type from static to dynamic,
since we don't have enough information at compile-time to emit
fully qualified tokens.  Previously, Coconut only supported dynamic
loads with object targets, however we will need to support the full
scope search (class, module, global, etc).
2017-04-08 07:30:38 -07:00
joeduffy 97c196f38a Don't mangle dictionary key names
The translation from Pythonic underscore_casing to camelCasing is
meant to only apply to serialized AST nodes, not arbitrary map keys
inside of dictionaries within them.  This change fixes the previous
behavior, which indiscriminately applied mangling throughout.
2017-04-08 07:12:23 -07:00
joeduffy 2b1c43569b Produce qualified tokens for exports 2017-04-08 07:07:18 -07:00
joeduffy 4f7b44e094 Make CocoPy a proper Pip package 2017-04-08 06:53:08 -07:00
joeduffy b2f2c4adb1 Produce valid Cocopacks
As of this change -- just a few minor fixes -- CocoPy produces real Cocopacks!

	$ cocopy index.py | coco pack info -a -
	// Basic example of an AWS web server accessible over HTTP.
	package "webserver" {
	    dependencies [
	        aws: "*"
	    ]
	    module "index.py" {
	        imports [
	            "coconut/aws"
	        ]
	        exports [
	            ".init" -> ".init"
	            ".main" -> ".main"
	            "_Name" -> "__name__"
	            "main" -> "main"
	        ]
	        method ".init": ()
	        method ".main": ()
	        property "_Name": dynamic
	        method "main": (): dynamic
	    }
	}

The contents of various tokens aren't quite right yet (they must be
fully qualified), meaning the pack still won't verify and load.

But we're getting close...
2017-04-07 13:32:12 -07:00
joeduffy f1c1684d6f Assert AST types more aggressively
This change adds a lot more aggressive AST type assertions throughout,
ensuring that we catch type mismatches as early as possible.

I realize this is borderline "un-Pythonic", but it will keep me sane.
2017-04-07 13:18:34 -07:00
joeduffy b6e34d661f Make a few minor fixes to CocoPy
* Refactor the options on encoding into an options object, to make
  it more future proof and a little easier to deal with.  (For instance,
  we hadn't plumbed the individual parameter options all the way
  through in all cases, an easy mistake to make when there are N of them.)

* Produce ast.TypeToken objects in a few requisite places.

* Replace the "." delimiter in Python module names with the CocoPack
  equivalent, "/".
2017-04-07 12:43:03 -07:00
joeduffy 3c50ee8c84 Add JSON encoding for serializing package and AST nodes 2017-04-07 10:38:17 -07:00
joeduffy f0535f0f00 Make progress on CocoPy's AST lowering
This change gets us far enough along to fully parse and lower the
Coconut package metadata for the webserver-py example.  It doesn't
serialize everything properly yet (since we haven't implemented the
serializer), and so it isn't runnable, but we're getting close(r).

* Fix a few minor issues in the AST layer:
    - is_* functions should accept self parameters.
    - All ast.Definition nodes require a name.
    - A few assertions were wrong (e.g., is_stmt vs is_statement).

* Refactor the op constants to be a little shorter.  I found them
  to be gratuitously verbose in calling code.

* Add a compiler.Context object for transformation passes.  This
  holds state specific to a given module's context, since -- as
  soon as we do multi-module passes -- it must remain isolated.

* Introduce support for the special "__name__" Python variable.
  This entails creating the variable and initializing it.  For now,
  since we only support single-module cases, it is always "__main__".

* Properly spill declarations and initialization statements in
  the module visitation logic.  This includes detecting global
  module properties while visiting statements at the top-level,
  which gives us an approximation to Python's oddly-whacky scoping
  rules (emulating this perfectly is going to be a PITA, I think).

* Properly track and emit imports.

* Add some more defensive assertions throughout, but particularly
  in ensuring that the right node types are flowing around
  (mostly this means statements vs expressions).

* Support the following Python AST types:
    - Blocks.
    - Assignments.
    - Module-scoped function definitions.
    - Imports.
    - "Attribute" access (a.k.a., property access, i.e., `x.y`).
    - Function calls.
    - Comparison operators.
    - Names and Name IDs.
2017-04-06 17:22:23 -07:00
joeduffy 5a2d18f0ee Add a smattering of Python transformations
This change mostly sketches out placeholders for the various Python
AST nodes and their transformations.  A smattering of them have been
implemented -- basically, those that were "trivial and obvious" upon
the first pass over this -- however, the goal of this checkin really
is to make the broad brush strokes that we will now fill in.

A few minor changes are included, like better assertions, a few
fixups to required/optional parameters, stringification and equality
checking functions, and so on.

Additionally, a bit of the logic for transforming modules is included.
For example, we shove statements into the module initializer, wire
up the entrypoint routine, etc.  For the most part, this is not
functional, however, and will get a bit more robust in the next commit.
2017-04-06 10:11:44 -07:00
joeduffy b61ff7b5db Rename some stale comments 2017-04-06 07:52:57 -07:00
joeduffy 056c4a49aa Eliminate conditional initialization
The prior code was trying to be too "clever", skipping initialization
of properties that haven't been given non-None values.  This was to
tidy up serialization and ensure the default JSON/YAML behavior skipped
undefined fields.  Although that basically works, it causes problems
elsewhere in the code, where certain fields would need to be lazily
initialized; this introduces a level of complexity I'd rather not have,
and furthermore, we will most likely need custom serialization anyway.
2017-04-06 06:54:30 -07:00
joeduffy 2bd3afc8ed Add the initial CocoPy compiler scaffolding
This change introduces a new CocoPy compiler.  It will implement a
subset of Python and translate it into Coconut packages and IL.

The project is underneath tools/cocopy/, alongside the cocojs/ compiler
directory, and is organized into two top-level directories: cmd/ contains
the command line tools and lib/ contains the library package code.

At the moment, the compiler processes a single file at a time.  The
Coconut package metadata is loaded identically to how CocoJS will discover
and load package metadata (including searching "upwards" in the filesystem
for package references).

Most of the Coconut package and AST metadata has been projected as Python
classes in the lib/ast and lib/pack modules.  The only missing pieces here
are the token manipulation abstractions which, for Python, will be far less
interesting due to the absence of any strong typing.  (Most likely we'll
end up with a bunch of runtime assertions instead of nice types.)

I've opted to use the pythonparser package because (a) it seems to be well-
designed, (b) it is meant for tools just like this, and (c) the Google
Grumpy compiler uses it (which at least tells me it's a safe-ish bet).

The transformer is still mostly an empty shell of placeholders.
2017-04-05 15:55:38 -07:00
joeduffy 99b94673c7 Merge branch 'master' of github.com:pulumi/coconut 2017-04-03 17:23:07 -07:00
joeduffy f0c1100ad6 Add basic web server example in Python 2017-04-03 17:22:37 -07:00
joeduffy 2451005b7c Fix an assert and a message
This change makes node optional in the lookupBasicType function, which is
necessary in cases where diagnostics information isn't available (such as
with configuration application).  This eliminates an assert when you fat-
finger a configuration key.  The associated message was missing apostrophes.
2017-03-30 15:06:55 -07:00
joeduffy 35f46dcdf7 Rename the infosec analyzer to contoso/infosec 2017-03-23 10:38:53 -07:00
joeduffy dccdcbd26b Shorten an error message 2017-03-23 08:15:10 -07:00
joeduffy d6fd6c244a Add the ability to output a plan as a DOT
We already had the ability to manually execute a CocoPack and generate
a DOT from its object graph.  However, for demo purposes we also want
to be able to generate one from the plan.  This adds a --dot flag to plan.
2017-03-23 08:10:33 -07:00
joeduffy 662404c1cb Require delete confirmations to match env name
This changes from "yes" to requiring an exact match of the
environment name, as is common in CLI tools like this.
2017-03-23 07:36:27 -07:00
joeduffy 3d74eac67d Make major commands more pleasant
This change eliminates the need to constantly type in the environment
name when performing major commands like configuration, planning, and
deployment.  It's probably due to my age, however, I keep fat-fingering
simple commands in front of investors and I am embarrassed!

In the new model, there is a notion of a "current environment", and
I have modeled it kinda sorta just like Git's notion of "current branch."

By default, the current environment is set when you `init` something.
Otherwise, there is the `coco env select <env>` command to change it.
(Running this command w/out a new <env> will show you the current one.)

The major commands `config`, `plan`, `deploy`, and `destroy` will prefer
to use the current environment, unless it is overridden by using the
--env flag.  All of the `coco env <cmd> <env>` commands still require the
explicit passing of an environment which seems reasonable since they are,
after all, about manipulating environments.

As part of this, I've overhauled the aging workspace settings cruft,
which had fallen into disrepair since the initial prototype.
2017-03-21 19:23:32 -07:00
joeduffy 2bda2d5b2d Tidy up the webserver example to match slides 2017-03-21 11:02:20 -07:00
joeduffy 98119f917e Rename ACMECorp security analyzer to InfoSec
This changes the example security analyzer from acmecorp/security
to just infosec, to reinforce that we will have certain analyzers
"out of the box" (infosec, cost, etc.)
2017-03-21 10:57:36 -07:00
joeduffy aee7f0ab92 Make a few minor renames in the webserver example 2017-03-16 09:17:52 -07:00
joeduffy 963df58912 Make a few nice modifications to the webserver example
* Rename the sample from ec2instance to webserver.

* Factor out the AMI map stuff into the AWS library, rather than the sample.

* Strongly type the instance type parameter using the aws.ec2.InstanceType union.

* Add a new webserver-comp example that demonstrates a bit of the ability to do
  encapsulation, componentization, and multi-instantiation.
2017-03-15 19:55:56 -07:00
joeduffy d23d3a5f87 Add the instance maps to the AWS library
For now, this makes the demo nicer; eventually, we probably want
to consider factoring this out into a separate "awsutils" package.
2017-03-15 19:54:29 -07:00
joeduffy 5d14430121 Don't count replacement steps unless explicitly requested 2017-03-15 16:56:23 -07:00
joeduffy 015730e9a9 Fix a bogus unchanged lookup
We need to look for the "old" resource, not the "new" one, when verifying
an assertion that a dependency that is seemingly unchanged actually is.
2017-03-15 16:46:07 -07:00
joeduffy 432105d627 Add an enum union type for AWS instance types 2017-03-15 16:12:24 -07:00
joeduffy e091bde692 Add a plan command; move env destroy to just destroy
This change adds a `coco plan` command which is simply a shortcut
to the more verbose `coco deploy --dry-run`.  This will make demos
flow nicer and elevates planning, an important activity, to a more
prominent position.  The `--dry-run` (aka `-n`) flag is still there.

This change also renames `coco env destroy` to just `coco destroy`.
This is consistent with deploy and plan being at the top-level.  We
now use `coco env` purely for evironment management commands (init,
config, rm, etc).
2017-03-15 15:40:06 -07:00