Using existing JavaScript code in Milescript
external class basics
Milescript allows you to declare classes as external, indicating that their implementation will supplied as part of the JavaScript environment, and that the Milescript compiler should not attempt to generate any JavaScript code to implement the class. To this end, external Milescript classes are much like abstract classes in that they can derive from other classes (that must also be external), define constructors, member variables, and member functions without function bodies. Unlike abstract classes, external classes can be instantiated, and they do not allow any member function to have a function body.
A good example of an external class is com.milescript.dom.window. Every JavaScript-capable browser supplies an implementation of window that's always available. This class's usage pattern is one of a static singleton - you cannot instantiate an instance of it, and it supplies a set of functions that can be called on it statically (i.e. without an instance). A snippet of the definition of this class is given below:
package com.milescript.dom; public external class window { // ... other functions and variables public static external void alert(String message); }
This allows other Milescript code to call the existing, environment-provided window.alert() in a type-safe manner:
package com.milescript.example; import com.milescript.dom.window; public class Test { public void callAlert(String message) { window.alert("Calling alert with message: " + message); }
If you wrote the following code, the compiler would issue compilation errors:
package com.milescript.example; import com.milescript.dom.window; public class Test { public void bogusAlertCall(int foo) { window.alert(foo); // ERROR: cannot implicitly convert int to String } public void bogusAlertCall2(String messageOne, String messageTwo) { window.alert(messageOne, messageTwo); // ERROR: No method on class window with signature alert(String, String) } }
Test.bogusAlertCall2 should, quite obviously, cause a compilation error. You could argue that Test.bogusAlertCall should not, however, because it's not too big a stretch to imagine implicit conversion of an int to a String. And regardless, the underlying implementation of window.alert will be able to handle it. For the sake of example, you could overload window.alert in your external Milescript class as such:
package com.milescript.dom; public external class window { // ... other functions and variables public static external void alert(String message); public static external void alert(int code); }
Of course, the example is a bit contrived with the simplicity of converting int's to Strings. However, the intent is obvious - Milescript allows you to declare external classes to interface with existing JavaScript code in a type-safe manner.
Creatable external classes
The example above demonstrates using an external class to interact with JavaScript code that provides methods and variables to use without requiring instantiation. It is also possible to use external classes that are creatable. The only extra step that needs to be done is to define constructors for the type. For this example, we'll look at the core.RegExp class. Much like com.milescript.dom.window, the browser's JavaScript environment is going to provide the implementation of RegExp. However, unlike window, RegExp must be instantiated to be used. It's class definition is like this:
package core; public external class RegExp { // Constructors for RegExp public external RegExp(String expression); public external RegExp(String expression, String flags); // Instance methods for RegExp public external void compile(String pattern); public external void compile(String pattern, String flags); }
This definition of RegExp contains two constructor definitions - functions with the same name as the class, and no return value. Also, none of the functions for RegExp are declared as static, meaning that they can only be called on an instance of RegExp. The following code demonstrates creating and using an instance of RegExp:
package com.milescript.example; public class Test { public boolean useRegExpToTest(String toTest) { RegExp re = new RegExp("[0-9]+"); return re.test(toTest); } }
Note here that we use the new operator to construct an instance of RegExp, and call the test method of that newly constructed instance. The following code will cause compilation errors:
package com.milescript.example; public class Test { public boolean useRegExpIncorrectly(String toTest) { return RegExp.test(toTest); // ERROR: test() is an instance method, not a static method! } }
Location of external classes in JavaScript environment
All external classes are, by default, treated by the code generator as existing at the root of the JavaScript environment's namespace. For instance, when code is generated to access the external class com.milescript.dom.window, the generated JavaScript code looks pretty much like this:
window.alert("Hi");
This is acceptable for using external classes that wrap intrinsically available classes in JavaScript like window, Document, HTMLElement, etc... However, this is most certainly not appropriate for classes defined in JavaScript somewhere besides the root namespace. A good example of a class being defined other than the root namespace can be seen in Google's AJAX JavaScript API. Let's take a look at the JavaScript necessary to create a simple Feed object using this API along with Google's AJAX Feeds API:
The first step is include a <script> tag in your HTML page to include the JavaScript for the Google AJAX API loader. The next is to initialize the Google Feeds API using the loading mechanism provided by the Google AJAX API loader script:
google.load("feeds","1");
That snippet doesn't given us much of a headache. If this were all there was to it, we'd simply implement an external class named google in any Milescript package we liked:
package wrap.google.ajaxapi; public external class google { public static external load(String apiName, String apiVersion); }
But wait - there is more to it. The Google AJAX API does some namespacing of its own! (This is, I think, a good idea for pure JavaScript code.) When the Google AJAX API loader loads the "feeds" API like you asked it to, it adds both the creatable classes for the API and utility functions to a new namespace google.feeds. The structure, in JavaScript, is similar to the following (I may leave a few things out, but you get the idea):
google
|
|-> load(apiName, apiVersion)
|
|-> setOnLoadCallback(callbackFunction)
|
|-> feeds
|
|-> Feed
| |
| -> Feed(feedUrl)
| -> load(callbackFunction)
| -> setNumEntries(numEntries)
| -> setResultFormat(format)
| -> includeHistoricalEntries()
|
|-> FeedControl
| |
| |-> FeedControl()
| |-> draw(element, options)
| |-> setNumEntries(num)
| |-> setLinkTarget(target)
|
|-> findFeeds(query, callback)
|
|-> lookupFeed(url, callback)
|
|-> getElementsByTagName(node, ns, localName)
Note that google serves as a container for both statically-available functions like load and other containment structures like feeds. Likewise, the google.feeds container structure contains statically available functions (findFeeds) and creatable class structures like Feed.
In Milescript (much like Java), only classes may contain functions. Classes themselves can only be contained by packages (we have yet to implement nested classes in Milescript - but if/when we do, it won't change this argument, as solving this conundrum with a giant class with tons of nested classes seems to be a pretty inelegant design). And, finally, packages can only be contained by other packages. JavaScript has no such restrictions on what can contain what. Therefore, we need a way to be able to create Milescript classes such that they can be used to represent structures like Google's above while still honoring the "packages contain packages or other classes, classes contain functions" containment layout enforced by Milescript. (Google are not the only folks using this pattern above, which is why it's necessary for Milescript to support it natively and cleanly.)
The at class modifier
This is where the at keyword comes into play. By using the at keyword in your external class declaration, you tell the JavaScript code generator where to locate the class you are declaring in the JavaScript environment. The at keyword must be followed by one of the following:
| declared | Indicates that the external class is located in a JavaScript namespace that is the same as the package declaration for the external class |
| (dotted reference) | A reference of the form (other.namespace.reference) that indicates the JavaScript namespace at which the external class is located. The dotted reference is like other dotted references - it must be a non-empty set of valid identifier names separated by a '.', and can neither begin nor end with a '.'. |
Given the above layout of the public classes and functions provided by the Google Feeds AJAX API, let's look at an example external class declaration that would allow usage of the google Feed class from Milescript code. First, we'll reiterate some important notes on this class:
- Feed is creatable - you must create an instance of it using new in order to use it
- Feed is located at google.feeds.Feed in the external JavaScript namespace
- The external google.feeds.Feed namespace contains both creatable classes like Feed and FeedControl and statically-available functions like findFeeds
Keeping these three things in mind, we can construct an external Milescript class declaration as follows:
package wrap.google.feeds_v1;
public external class Feed at (google.feeds) {
public static external Object JSON_FORMAT;
public static external Object XML_FORMAT;
public static external Object MIXED_FORMAT;
public external Feed(String feedUrl);
public external void load(FeedLoadedDelegate feedLoaded);
public external void setNumEntries(int num);
public external void setResultFormat(Object resultFormat);
public external void includeHistoricalEntries();
}
The first thing to note here is that we have declared Feed as an external class. We've placed it in a package called wrap.google.feeds_v1, giving a pretty good indication to coders coming along and looking at this code that Feed is an external class wrapping the Google Feeds version 1 implementation of Feed. Note that we've also used the at keyword in the declaration of the Feed class, indicating to the Milescript code generator that this Feed class is located at (google.feeds) - meaning that when the code generator wants to reference this class, it should do so with google.feeds.Feed, not just Feed.
Next, note that we've defined a constructor for the Feed class, indicating that instances of this class should be passed a String upon construction. Combined with the information supplied by at, the code generator will now transform the Milescript code:
Feed feed = new Feed("http://some/url/to/a/feed");
into the JavaScript code:
feed = new google.feeds.Feed("http://some/url/to/a/feed");
Also note the static member variables declared on Feed above. These are static constants provided by the Google Feeds AJAX API used to indicate what format you'd like feed lookup returned as. They can be used in Milescript with code such as
Feed feed = new Feed("http://...");
feed.setResultFormat(Feed.JSON_FORMAT);
which the code generator will translate to
feed = new google.feeds.Feed("http://...");
feed.setResultFormat(google.feeds.Feed.JSON_FORMAT);
Since our Feed Milescript class is really just a wrapper, note that we've not particularly constrained the type of the three *_FORMAT static members, nor the type of the single parameter to Feed.setResultFormat. Constraining these values to a given type (we use Object, which stands in for anything) would not be appropriate for our wrapper class in this case, as Google does not make apparent the underlying type of the values for these members as a part of its public API. We could certainly poke around and find out their types as of this moment, but since the types aren't a part of the public API "contract", it is more proper to treat them as values opaquely produced and consumed by the Google Feeds API itself.
The nameless class modifier
So now at allows us to declare external classes that represent JavaScript class structures wherever they may be in the JavaScript namespace. We're still left with a problem: how do we access functions located at the same place in the JavaScript hierarchy as classes? We can't simply write another plain old external class, because the container of the function is also a container of classes, and must therefore be represented in Milescript as a package. An even more apparent example would be a callable function located at the root of the JavaScript namespace - you can't wrap it in a regular external declaration, as it is not contained by anything.
To deal with this, we introduce the nameless external class modifier. When placed on an external class, it instructs the JavaScipt code generator to not use the external class's name when generating code that calls the function. external nameless classes are essentially collections of callable functions at a place in the JavaScript hierarchy that contains both creatable classes and callable functions.
As an example usage of nameless, lets look at a Milescript class that makes available the three functions lookupFeed, findFeeds, and getElementsByTagNameNS. Keep in mind that these functions, like the creatable classes FeedControl and Feed are located in the namespace google.feeds.
package wrap.google.feeds_v1;
import com.milescript.xml.Node;
import com.milescript.xml.NodeList;
public static external nameless class FeedsFunctions at (google.feeds) {
public static external void findFeeds(String query, FindFeedsDelegate callback);
public static external void lookupFeed(String url, LookupFeedDelegate callback);
public static external NodeList getElementsByTagNameNS(Node node, String namespace, String localName);
}
Note that FeedsFunctions' declaration uses both the nameless and at keywords. These two keywords in conjunction tell the code generator to locate the members of FeedsFunctions at google.feeds, and to not use the name FeedFunctions when locating them.
As a straightforward example, the following Milescript code:
FeedsFunctions.findFeeds(...);
would be generated as:
google.feeds.findFeeds(...);
