Introduction
The Jet engine is a multi-media platform targetting WebAssembly. It supports an ECMAScript 4 like language, a very modified version of ActionScript 3; and a Cascading Style Sheet 3 dialect.
Unlike Adobe Flex, Jet uses reactive user interface.
Features
Event model
When compared to standard ActionScript 3, the event model has been simplified due to type inference, and uses shorter notation for the event listener methods.
Documentation comments
Documentation comments support Markdown and referencing local images.
Package manager
A productive package manager is supported which facilitates creating new projects and building them. In addition, published packages are automatically queued for documentation generation in the online documentation host.
Reactivity
The ECMAScript for XML (E4X) feature of ActionScript is modified such that the markup syntax is used for implementing UI components, similiar to ReactJS, but tied to Jet Display List.
The E4X markup supports comments and XML namespaces.
Comparison to JavaScript
- Vanilla JavaScript ∉ typed
- Vanilla JavaScript ∉ proper custom events
- JavaScript ∈ immediate classes
- TypeScript ∉ custom event inheritance (requires
...BaseClassEvents
) - TypeScript ∉ built-in reactivity
ActionScript language guide
The Jet engine uses ActionScript 3 as its main scripting language. The language supports many new features compared to its standard.
Packages
ActionScript packages are used for organizing definitions, using left-to-right hierarchic names.
package { public var x = 10 }
package me.diantha { public var y = 15 }
// (top-level).x
x
import me.diantha.*;
// me.diantha.y
y
me.diantha.y
Default scope
The language's default scope imports the top-level package, and it is the parent scope of the scope in each source file; therefore it is possible to override the name of top-level items, such as Number
, Array
and parseInt
.
global
The global
namespace equals the public
namespace of the top-level package. It may be necessary when an item overrides a top-level item, as in:
package q.f {
public const Number : global::Number = 10;
}
Types
Wildcard
The *
type means untyped, and accepts all possible values in the language.
*
void
void // undefined
null
null // null
String
String is a UTF-16 encoded sequence of characters.
String
Boolean
Boolean // false or true
Number
IEEE 754 double-precision floating point.
Number
BigInt
Arbitrary range integer.
BigInt
float
IEEE 754 single-precision floating point.
float
decimal
IEEE 754 quadruple-precision floating point (binary128).
decimal
int
int // signed 32-bit integer
uint
uint // unsigned 32-bit integer
Tuple
Tuples, when untyped, have the length
property available.
[T1, T2]
Array
The Array
type represents a dynamic list of elements, optimized for when T
is a number; for instance, [uint]
will use a growable buffer optimized specifically for 32-bit unsigned integers.
[T]
Structural function
Structural function types inherit from Function
.
function(T1, T2=, ...[T3]):E
Union
(Ta, Tb)
Map
Map.<K, V>
Usage instance:
const map = new Map.<String, Number>();
map.x = 10;
const fns = new Map.<String, Function>();
fns.m = function() { return 10 };
trace(fns.call("m"));
Note: Property access on a
Map
equals data access. Method call on aMap
equalsMap
method use.
Structural object
Structural object types are compiled into efficient structures.
{
/** x */
x:Number,
/** y */
y?:Boolean,
}
...rest
components may appear, where rest
must be another structural object type; the resulting type may be a subtype of rest
depending on whether properties do not collide.
type A = { x:Number };
type B = { y:Number, ...A };
type U = { ...W, ...B };
type W = { z:Number };
Objects
All types except void
, null
, uint
, int
, float
, Number
, decimal
, BigInt
and Boolean
represent referenceable objects that may be null
. The Object
class is inherited by all types, except *
, void
, null
and unions.
The Object
class, unlike in standard ActionScript, is not untyped and there is no support for dynamic
classes; therefore, it is preferable to use the *
type when it is necessary to access prototype properties such as constructor
and toString()
.
Type conversion
Optional conversion
An optional conversion will return the default value of T
on conversion value.
v as T
Forced conversion
A forced conversion throws a TypeError
on conversion failure.
T(v)
Type matching
Type test
v is T
Switch
switch type (v) {
case (n:Number) {
trace("number");
}
default {
trace("any other");
}
}
Classes
Inheritance
By default a class extends Object
. Use the extends
clause for extending a specific class:
class B extends A {}
Constructor
By default the constructor is based in the base class's constructor and will initialize the instance with default property values.
Define a constructor for a class using its name in a function definition:
class A {
public function A() {}
}
Abstract classes
abstract class A {
abstract function m():void;
}
Static classes
static class MyNamespace {
public static const VALUE:Number = 10.5;
}
Final classes
final class A {}
class B extends A {} // ERROR!
Override
class A {
function m() {}
}
class B extends A {
override function m() { trace("B!") }
}
Final method
class A {
final function m() {}
}
class B extends A {
override function m() { trace("B!") } // ERROR!
}
Super
class A {
function A() { trace("A!") }
function m() { trace("A.m!") }
}
class B extends A {
function B() { super(); trace("B!") }
override function m() { super.m(); trace("B.m!") }
}
Namespaces
ActionScript 3 uses three-dimensional property names: a property consists of a namespace and a local name.
Private access
You can use ActionScript namespaces to privatize definitions across class fields and access them from any package as long as the used namespace is in scope:
// MyLibInternals.as
package my.lib {
/**
* @private
*/
public namespace MyLibInternals = "http://my.lib/internals";
}
// Atom.as
package my.lib.atoms {
import my.lib.*;
public class Atom {
/**
* @private
*/
MyLibInternals var x:Number = 10;
}
}
// consumer.as
import my.lib.*;
import my.lib.atoms.*;
const atom = new Atom();
trace(atom.MyLibInternals::x);
Events
The native EventEmitter
class is designated for dispatching and listening to events, and is the one used for implementing the hierarchic event model in the display list API.
In addition, the IEventEmitter
interface may be implemented instead of extending the EventEmitter
class.
An event emitter
/**
* On play event.
*/
[Event(name="play", type="Event")]
/**
* My player class.
*/
class Player extends EventEmitter {
public function aMethod() {
this.emit(new Event("play"));
}
}
Player
usage:
player.on("play", function() { trace("played") });
Note: Unlike in Flash Player, the convention of using static constants for identifying event types is discarded.
An event class
Event constructors must always take the event type as the first argument; any other arguments may follow.
class SomeEvent extends Event {
// constructor
public function SomeEvent(type: String) {
super(type);
}
}
Emitting
The EventEmitter#emit()
method is defined as follows:
public function emit.<E extends this.Event::object>(e:E) : Boolean {
// code
}
When the emit()
method is used, it will force a new E(...)
expression to be a correct Event
object construction, by ensuring the first argument identifies a determined event type according to E
.
Listening
The EventEmitter#on()
method is roughly defined as follows:
public function on.<E extends this.Event::type>(
type: E.name,
listener: function(E.type):void,
) : void {
// code
}
Environment variables
Environment variables may be read from the project's .env
file using the import.meta.env.VAR_NAME
expression:
import.meta.env.FOO
Embed
The Embed()
expression may be used for embedding static media into the program.
const text : String = Embed("data.txt");
Note: In Flash Player, an
[Embed]
meta-data was used in definitions in order to embed static files; this is not the case with Jet engine.
MIME type
Through type inference, the Embed()
expression results in either String
(UTF-8 interpretation) or ByteArray
(octet stream).
Conditional compilation
NAMESPACE::CONSTANT
may match a set configuration constant.
NAMESPACE::CONSTANT {
//
}
NAMESPACE::CONSTANT var x
Inline constants:
trace(NAMESPACE::CONSTANT)
Asynchronous code
function myMethod(): Promise.<Boolean> {
return await other();
}
ECMAScript for XML
ECMAScript for XML (E4X) comprises XML markup and XML data manipulation facilities.
Markup
XML markup is directly used in ActionScript 3 for rendering reactive UI components.
package me.diantha.portfolio {
public function Portfolio() {
return (
<>
<j:VGroup xmlns:j="jet.components.**" gap={5}>
<!-- some comment -->
<j:Label>
<markdown>
Hi **there**.
</markdown>
</j:Label>
</j:VGroup>
</>
);
}
}
XML namespaces are used for importing packages solely within the markup, either non-recursive (q.f.*
) or recursive (q.f.**
). It is additionally allowed to use lexical names and fully qualified names like <jet.components.HGroup>
as long as the component name is in scope.
Note: Unlike the E4X standard 2nd edition, markup does not result in
XML
orXMLList
objects, but ratherReactNode
; and there is support fora
attributes without an attribute value, which equalsa={true}
.
Markdown
Use <markdown>
tags for translating Markdown to HyperText Markup Language (HTML) text.
Note: Tags nested with
<markdown>
must comply with XHTML tags; for instance, use<br/>
instead of<br>
.
It is additionally allowed to interpolate HTML code inside a <markdown>
tag using braces:
<markdown>
Hi, {personName}
</markdown>
Note: Interpolated parts must be in the HTML language (not XHTML or Markdown).
ASDoc
Documentation comments use the format /** */
, with lines optionally beginning with *
.
Supported tags
@copy
Copies documentation comment from another definition. Use a #x
component to refer to an instance property.
@copy A
@copy A.w
@copy A#x
@copy #x
@deprecated
@deprecated [Description]
@internal
Internal comment for an item (not included in the generated documentation).
@internal Comment.
@inheritDoc
Inherits documentation from base class or base class's item.
@inheritDoc
@see
Where item
maybe an item reference with optional #x
instance property, or just an instance property #x
.
@see item [Display text]
@param
@param paramName Description
@private
Hides an item from the generated documentation.
@private
@return
@return Description
@throws
@throws ClassName [Description]
Statements
For..in
Iterate keys:
for (var key in o) {
//
}
Iterate values:
for each (var value in o) {
//
}
Import
All of a pacakge:
import q.f.*;
Specific property:
import q.f.x;
Alias:
import x1 = q.f.x;
Alias a package:
import f = q.f.*;
f::x
Alias a package recursively:
package q.f.w {
public var x = 10;
}
import f = q.f.**;
f::x
Switch
The switch statement, as opposed to the standard, does not support fallthrough in cases.
switch (0) {
case 0: {
trace("zero");
}
case 1: {
trace("one");
}
}
The above prints "zero", not "zero" then "one".
Switch type
switch type (v) {
case (d:Date) {
//
}
default {
//
}
}
Try
try {
//
} catch (e: SecurityError) {
//
} catch (e: RangeError) {
//
} finally {
//
}
Expressions
String literal
Triple string literal
Triple string literals span multiple lines and are indentation-aware.
const text =
"""
a
"""
assert(text == "a")
Display List
The Display List is a hierarchy of two-dimensional display objects.
Points and scale factor
A point is a 1/72nd of an inch.
Scale factor
In the display list, all the point units are affected by the global scale factor (ScaleFactor#scale
).
scaleFactor.on("change", function() {
//
});
scaleFactor.scale = 10;