One guiding principle for what makes it into the MuIL AST is that
the gap between source language and AST should not be too great; the
projection of switch statements from MuJS into MuIL clearly violated
that principle, particularly considering that the logic wasn't even
right due to the incorrect emulation of conditional breaks.
Instead of digging deeper into the hole, I've encoded switch logic
in the AST, and implemented support in the evaluator.
* Add a TODO as a reminder to implement number toString formatting.
* Change the Loreley delimiters to something obscure ("<{%" and "%}>")
to avoid conflicting with actual characters we might use in messages.
Also, make the assertions more descriptive should Loreley fail.
* Rip out a debug.PrintStack() in verbose logging.
* Check the underlying pointer's object type for +=, not the pointer type.
This change implements intrinsic function support in the runtime.
The basic idea is to swap out references to MuIL functions with
runtime-implemented functionality, for operations that cannot be
expressed in the MuIL-subset. For example, this change includes
two such cases: 1) a mu.runtime.isFunction(obj) function to check if
the target object is a function; and 2) a
mu.runtime.dynamicInvoke(obj,obj,obj[]) function to dynamically
invoke a target object.
These will be used most immediately to implement ECMAScript toString
functionality in the MuJS runtime library, however it will also come
in handy shortly to implement things like printf (marapongo/mu#86),
string and array functions (marapongo/mu#80), and, eventually, the
ECMAScript-specific operators and behavior (marapongo/mu#61).
This change generalizes the support for default modules (added as
part of marapongo/mu#57), to use an "alias" table. This is just a
table of alternative module names to the real defining module name.
The default module is now just represented as a mapping from the
special default module token, ".default", to the actual defining
module. This generalization was necessary to support Node.js-style
alternative names for modules, like "lib" mapping to "lib/index".
Previously, I had thought we would ask MetaMu compilers to map their
own exception/error types to ours. That causes some complexity, however,
in that the exception types in each language (if they even exist) are
"different". So we would need to wrap things, etc., which seems cumbersome.
Partly I had done this after having been burned on the CLR by permitting
throws of any types; e.g., C++/CLI could throw integers, which would rip
through C# code unknowingly, because all C# throws had to derive from the
Exception base class. This actually caused some security issues!
But, we're in a different place, and a different time. The first three
languages I envision supporting -- ECMAScript, Python, and Ruby -- each
permit you to throw anything. And the only other languages I am seriously
contemplating right now -- Go -- doesn't even have exceptions.
So this change backs off and is less opinionated about what is thrown.
Instead, we track stack traces, etc., in the unwind information, and happily
propagate whatever object is thrown.
This change more accurately implements ECMAScript prototype chains.
This includes using the prototype chain to lookup properties when
necessary, and copying them down upon writes.
This still isn't 100% faithful -- for example, classes and
constructor functions should be represented as real objects with
the prototype link -- so that examples like those found here will
work: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor.
I've updated marapongo/mu#70 with additional details about this.
I'm sure we'll be forced to fix this as we encounter more "dynamic"
JavaScript. (In fact, it would be interesting to start running the
pre-ES6 output of TypeScript through the compiler as test cases.)
See http://www.ecma-international.org/ecma-262/6.0/#sec-objects
for additional details on the prototype chaining semantics.
The complexity of the last round of property initialization code was
starting to bug me. I've switched away from lazily initializing them
and now eagerly initialize at the proper times: module initialization,
class initialization (for statics), and object allocation (for instances).
This includes the logic around readonly and freezing. The code is a
lot simpler now and actually enforces some invariants that we'd like to
enforce, like not silently adding new properties when a supposedly static
member load is happening (all such accesses should be dynamic ones).
This change accurately enforces readonly properties. Namely, they
should not be written to anywhere outside of the respective initializer,
but writes must be allowed within them. This means the module initializer
for module properties, the class initializer for statics, and the object
constructor for instance properties.
Now that some name lookups are context-sensitive (namely, whether
or not we should use the export or member table for inter vs. intra
module token references), we need to faithfully track the context.
This change fixes up some issues in the big export refactoring;
namely, we need to chase down references one link at a time during
binding, before they settle. This is because an export might depend
on another export, which might depend on ...
This change redoes the way module exports are represented. The old
mechanism -- although laudible for its attempt at consistency -- was
wrong. For example, consider this case:
let v = 42;
export { v };
The old code would silently add *two* members, both with the name "v",
one of which would be dropped since the entries in the map collided.
It would be easy enough just to detect collisions, and update the
above to mark "v" as public, when the export was encountered. That
doesn't work either, as the following two examples demonstrate:
let v = 42;
export { v as w };
let x = w; // error!
This demonstrates:
* Exporting "v" with a different name, "w" to consumers of the
module. In particular, it should not be possible for module
consumers to access the member through the name "v".
* An inability to access the exported name "w" from within the
module itself. This is solely for external consumption.
Because of this, we will use an export table approach. The exports
live alongside the members, and we are smart about when to consult
the export table, versus the member table, during name binding.
This commit implements array l-values (for dynamic loads only, since we do
not yet ever produce static ones). Note that there are some ECMAScript
compliance noteworthies about this change, which are captured in comments.
Namely, we are emulating ECMAScript's ability to index into an array
anywhere (except for negative numbers, which is a problem). This is in
contrast to the usual approach of throwing an error on out-of-bounds access,
which will crop up when we move on to other languages like Python. And yet we
are usuing a real array as the backing store, which can cause problems with
some real ECMAScript programs that use sparse arrays and expect the "bag of
properties" approach to keep memory usage reasonable.
The work item marapongo/mu#70 tracks this among other things.
This change plucks array element types out of more AST nodes, tightening
up the MuIL that MuJS produces. It also adds a few cases where we should
be emitting "object", and permits more dynamic conversions (which are
technically unsafe, but necessary for the sort of duck typing we anticipate).
In select few cases (right now, just nulls and boolean true/false),
we use predefined object constants to avoid allocating wastefully.
In the future, it's possible we will do this for other types, like
interned strings. So, the graph generator should tolerate this.
This change starts tracking all objects in our MuGL graph. The reason is
that resources can be buried deep within a nest of objects, and unless we
do "on the fly" reachability analysis, we can't know a priori whether any
given object will be of interest or not. So, we track 'em all. For large
programs, this would obviously create space leak problems, so we'll
eventually, I assume, want to prune the graph at some point.
With this change, the EC2instance example produces a (gigantic) graph!
This change implements stack traces. This is primarily so that we
can print out a full stack trace in the face of an unhandled exception,
and is done simply by recording the full trace during evaluation
alongside the existing local variable scopes.
In dynamic scenarios, property keys could be arbitrary strings, including
invalid identifiers. This change reflects that in the runtime representation
of objects and also in the object literal and overall indexing logic.
This change "kicks the can" of dealing with `dynamic` down the road.
Namely, the binder will be permissive about letting dynamic objects
flow through to the evaluation phase. At that point, the evaluator
will be responsible for performing duck typing, etc.
This change introduces support for dynamic object literals. If the
type of a literal is dynamic, then the property initializers for that
literal are naked strings; if the type of a literal is anything else,
however, we expect that the property names are full blown member tokens.
This change prepares to mimick the TypeScript behavior of `any` which,
it turns out, is closer to Python and Ruby duck typing than what we have
here. It also introduces `object` to mean "the base of all types".
MuJS has been updated to map `any` to `dynamic`.
At this point, the conversion logic now distinguishes between NoConversion,
ImplicitConversion, and AutoCastConversion cases, the latter being a case
in which `dynamic` is the source and so a duck-type-style cast must occur.
Right now we do not honor the AutoCastConversion case anywhere, so we still
have not achieved the duck-typing semantics of the source language.
This is part of fixing marapongo/mu#79.
After a bit more thinking, we will create new SDK packages for each
of the languages we wish to support writing resource providers in.
This is where the RPC goo will live, so I have created a new sdk/
directory, moved the Protobuf/gRPC definitions underneath sdk/proto/,
and put the generated code into sdk/go/ and sdk/js/.
This change moves the RPC definitions to the pkg/murpc package,
underneath the proto folder. This is to ensure that the resulting
Protobufs get a good package name "murpc" that is unique within the
overall toolchain. It also includes a `generate.sh` script that
can be used to manually regenerate client/server code. Finally,
we are actually checking in the generated files underneath pkg/murpc.
This change adds a --dot option to the eval command, which will simply
output the MuGL graph using the DOT language. This allows you to use
tools like Graphviz to inspect the resulting graph, including using the
`dot` command to generate images (like PNGs and whatnot).
For example, the simple MuGL program:
class C extends mu.Resource {...}
class B extends mu.Resource {...}
class A extends mu.Resource {
private b: B;
private c: C;
constructor() {
this.b = new B();
this.c = new C();
}
}
let a = new A();
Results in the following DOT file, from `mu eval --dot`:
strict digraph {
Resource0 [label="A"];
Resource0 -> {Resource1 Resource2}
Resource1 [label="B"];
Resource2 [label="C"];
}
Eventually the auto-generated ResourceN identifiers will go away in
favor of using true object monikers (marapongo/mu#76).