Overview
ShockScript is a strongly typed, multi-paradigm scripting language.
Note: The document is a work-in-progress, and may lack content.
E4X
ShockScript embeds the XML language, based on the legacy E4X standard. It is, besides XML processing, suitable for implementing reactive UI components.
For the Whack Engine, E4X literals may create Whack DS nodes in addition to the E4X data types.
Event
ShockScript incorporates a basic event model and allows easily declaring event types and documenting them. Implementations may extend the event model for working with a Document Object Model.
ShockDoc comments
ShockScript supports documentation comments allowing for Markdown notation, special tags such as @throws and media inclusion.
Abbreviation
ShockScript is abbreviated as SX and uses the conventional file extension “.sx”.
Embed
The Embed() expression may be used for embedding files and media into the program. Its default behavior is to return a data: (for small files) or external URL (for large files).
Embed("flower.gif")
Note: When returning an external URL, implementations such as Whack Engine use the
app://scheme to fetch a file in the application’s installation directory.
Note: Implementations may support an artifact path interpolation, as in:
Embed("{target}/auto.generated")That is useful when a build script is involved that generates files at the artifact path.
Forcing external embedding
With the following, files are never embedded inside the program, even if they are short:
Embed("flower.gif", external="true")
Where external files go and license
External files are typically embedded in a structured way in the final program, using project ID + resource path + filename, which is also useful for embedding licensed resources such as fonts.
Note: When using Whack Red, do not worry about web cache. Files respond with appropriate HTTP ETag (like a SHA-512 hash of the file contents), Last-Modified and Cache-Control headers.
Including statically
The user may embed files statically as either an UTF-8 encoded String, or ByteArray, or CSS node depending on the framework, rather than obtaining an URL.
For a String:
Embed("BeautySecrets.txt", type="text/plain")
For a ByteArray:
Embed("Hacker.bin", type="application/octet-stream")
For a style sheet node (like whack.ds.StyleSheetNode for Whack):
Embed("AppBarSkin.css", type="text/css")
Language comparison
This section compares ShockScript to other technologies.
ReactJS
ShockScript embeds the XML language closely as the E4X standard, however XML literals allow for implementations to produce anything desired based on type inference. The Whack DS framework for ShockScript, a reactive UI layer over the DOM, is based on the ReactJS principles and uses E4X constructs that may remind of JSX.
There are, however, many positive differences to ReactJS, such as memoization and auto dependency tracking.
HelloWorld.sx
package zero {
import whack.ds.UIComponent;
import s = spark.components.*
public class HelloWorld extends UIComponent {
public function HelloWorld() {
super()
final = (
<s:Application>
<s:Label>Hello World!</s:Label>
</s:Application>
);
}
}
}
Event handlers
In ShockScript, event handlers are expressed as e&={statementList} (note the ampersand &) as a shorthand to e={function(event){statementList}}. Furthermore, event handlers are conventionally expressed without an on prefix (for instance, click instead of onClick), and they are documented with the @event tag.
When specifying event handlers, note that the callback is cached only if .
Prefixes
ShockScript allows for <q:N>, whose name resolution equals q::N. Dots may be used for entities other than namespaces, as in:
<zero.components.AppBar/>
For brevity, you do either:
import zx = zero.components.*;
xn = <zx:AppBar/>
or:
import zero.components.*
xn = <AppBar/>
Interpolation
Interpolation works similiarly to ReactJS, except for HTML.
<div>
{undefined}
{null}
{node}
{node_list}
{plain_text}
{number} <!-- The Number union -->
{boolean}
</div>
Interpolating attributes uses { object } and not { ...object } and must appear at most once at the end of all attributes:
<button {arguments}>click me</button>
Component definition
Components are defined as classes extending the UIComponent class and not regular functions. It is far different from ReactJS legacy class components.
States
Unlike ReactJS, in Whack DS there is no risk of accessing an outdated state’s value, due to how states are constructed.
package spark.components {
import whack.ds.UIComponent;
public class Ark extends UIComponent {
[State]
var x : uint = 0;
public function Ark() {
super();
final = (
<w:VGroup>
<span>clicked {x} times</span>
<button click&={x++}>click me</button>
</w:VGroup>
);
}
}
}
The state’s initializer represents the state’s initial value.
Like ReactJS, there is no transitive detection of mutation methods; therefore, the following is necessary over an array .push(v):
x = [...x, v];
As to Map objects:
m = { ...m, k: v };
Bindables
In Whack DS the concept of “refs” is called bindables.
package spark.components {
import whack.ds.UIComponent;
public class Ark extends UIComponent {
[Bindable]
var button : org.w3.web.Button? = null;
public function Ark() {
super();
whack.ds.useEffect(function() {
trace(button!.@x);
});
final = (
<button bind={button}>click me</button>
);
}
}
}
Note .@x is a meta-data attribute accessor for DOM elements.
Contexts
Using contexts results in ContextReference.<T> objects, although they are used as natural Context-annotated variables.
public class View extends UIComponent {
[Context("zero.contexts.Example")]
const example;
public function View() {
super()
final = (
<></>
)
}
}
Props
Props must be typed tap {}. It is not recommended to destructure Props.
public class View extends UIComponent {
public function View(props : Props) {
super()
final = (
<></>
)
}
public type Props = tap {
/** @event */
next? : function() : void,
}
}
Effects
The popular “useEffect” hook, differently from ReactJS, auto tracks dependencies, preventing mistakes. For listening to any changes, use "*".
whack.ds.useEffect(function() {
//
return function() {
// Cleanup
};
});
whack.ds.useEffect(function() {
//
}, "*");
Styling
Unlike with ReactJS, there is built-in support for linking style sheets in a Whack DS component.
<div>
<fx:Style>
<![CDATA[
root {
background: red
}
]]>
</fx:Style>
</div>
Callbacks
Callbacks are cached within XML attributes for memoization, if it matters.
Helpful resources
MXML
The MXML language, as part of the Apache Flex framework, was used for describing UI components in an intuitive way. ShockScript uses XML constructs semantically similar to the ReactJS + JSX technologies, but designed to feel close to MXML.
HelloWorld.sx
package zero {
import whack.ds.UIComponent;
import s = spark.components.*
public class HelloWorld extends UIComponent {
public function HelloWorld() {
super()
final = (
<s:Application>
<s:Label>Hello World!</s:Label>
</s:Application>
);
}
}
}
Event handlers
In MXML, event handlers were expressed as e="statements". In ShockScript, they are expressed as e&={statements} (note the ampersand &) as a shorthand to e={function(event){statements}}.
Note: Although not demanded as such, as opposed to ReactJS + DOM, event handlers are conventionally expressed without a
onprefix, such asclick&={trace("clicked!")}rather than ReactJSonClick={e=>{console.log("clicked!")}}. Event props are conventionally given the@eventtag in the ShockDoc comments. Classes continue using theEventmeta-data, though without needing the@eventTypetag.
Rendering components
The Whack DS framework allows programmers to implement UI components as throwaway classes that wrap around the DOM elements of Whack Engine. The component is rendered by constructing the class initially and whenever states, props and/or contexts change.
Effects
Effect hooks like whack.ds.useEffect may be used to run code when specific Props, State or Context change, or solely for running code during the component mount and unmount phases.
The effect dependencies — States, Props and Contexts it relies on — are auto-tracked as the effect function evaluates.
whack.ds.useEffect(function() {
// Cleanup
return function() {
//
};
});
// Effect that runs everytime (*)
whack.ds.useEffect(function() {
//
}, "*");
When there are no dependencies, the hook is equivalent to a component mount/unmount event, with the unmount phase handled through the returned function.
whack.ds.useEffect(function() {
// Did mount
return function() {
// Unmount
};
});
Callbacks
Similarly to effects, callbacks that appear in E4X literals applied to the whack.ds.Node type auto track dependencies, since they are cached for aiding on memoization.
States
Declare State variables using the State meta-data:
public class Main extends UIComponent {
[State]
var counter : uint = 0;
}
The initial value of counter is zero, although that initializer evaluates only the first time the component renders.
Overwriting a state with a different value (as by an equals comparison) will indirectly re-render the component.
Note that, like with ReactJS, arrays and structures as states will not trigger a re-render during push like operations; instead the programmer needs to reconstruct the object, as in:
x = [...x, 10]
Bindables
In the top-level of a Whack DS component, declare bindables by using the Bindable meta-data. Bindables have certain use-cases, such as persisting a value across renders, and extracting DOM elements from certain tags (in which case the bind attribute is used).
public class Main extends UIComponent {
[Bindable]
var button : org.w3.web.Button? = null;
public function Main() {
super()
final = (
<button bind={button}>Click me</button>
)
}
}
Contexts
Obtain inherited contexts by using a Context variable.
public class Main extends UIComponent {
[Context("spark.contexts.Theme")]
const theme;
public function Main() {
super()
final = (
<></>
)
}
}
Capture safety
Unlike in ReactJS combined with TypeScript, states, bindables (“refs”) and context values (if following the recommendations) are captured by reference from nested functions, guaranting the “outdated” value of, say, a state, is never captured, which facilitates development by requiring no additional Bindable declaration.
Props are also safe to use anywhere within the component as long as you follow the recommendations (such as avoiding destructuring it in large method bodies).
Styling
Unlike with ReactJS, there is built-in support for linking style sheets in a Whack DS component.
<div>
<fx:Style>
<![CDATA[
root {
background: red
}
]]>
</fx:Style>
</div>
Helpful resources
ActionScript 3
ShockScript looks like ActionScript 3. This section describes several details that changed on the language.
Primitive types
- ShockScript does have more numeric types close to ECMAScript 4.
- The Boolean and String type names are as they are, although they are alternatively aliased
booleanandstringin the top-level package.
String type
The String type stores an UTF-8 encoded text, not an UTF-16 encoded text.
"\u{10ffff}".length // UTF-8 length
"\u{10ffff}".charAt(0) // Code Point at byte 0
"\u{10ffff}".charCodeAt(0)
for each (var ch in "shockscript".chars()) {
// ch:uint
}
"shockscript".chars().length() // Code Point length
Include directive
The include directive is not included in ShockScript. It was attempted before, even though it imposes certain efforts for language servers. That may be reconsidered in the future.
Dynamic
The Object type is not dynamic per se, nor does it contain undefined, nor are there dynamic classes, nor are there legacy ECMAScript prototype objects. Only the * type is dynamic and contains undefined.
Matching: The str.match resulting object is slightly different, but still supports indexing.
Obtaining constructor: o.meta::class()
Nullability
Types except * are non-nullable by default. Use “t?” as a shorthand notation for (t,null), and “t!” as a way to exclude undefined and/or null.
Overriding methods
- Instance methods may override another method and include additional optional parameters (including the rest parameter).
- Instance methods may override another method and return a more contravariant result type.
class A {
function m() {}
}
class B extends A {
override function m(...rest:[float]) {}
}
“in” operator
The in operator behaves differently. It triggers meta::has() which is in general used for determining whether a collection contains a specific value; for Maps it determines whether a pair key exists; for XML and XMLList objects it performs the same E4X behavior.
trace(e in arr);
trace(k in m);
Filter operator
The filter operator has been modified to use a * identifier rather than cluttering the lexical scope with dynamic names.
xn.(*.@x.startsWith("abc"))
With statement
The with statement is modified to use the * identifier to avoid cluttering the lexical scope.
with (o) {
*.x =
*.y = 10;
}
“this” capture
The this object is always captured from the parent activation in nested activations; there is no way to override the this object with another value.
class A {
function m() {
function n() {
// this:A
}
}
}
E4X
XML literals produce by default XML or XMLList unless the inference type is an implementation-defined component type (such as for the Whack Engine, whack.ds.Node). Such expressions have also undergone incremental syntax additions:
<t a/>equals<t a={true}/><t e&={}/>equals<t e={function(event){}}/>or<t e={function(){}}/>
<w:VGroup>
<h1>welcome</h1>
<button click&={trace("clicked me");}>Click me</button>
</w:VGroup>
Events
Events are declared without defining related static constants, as ShockScript performs vast type inference; thus, the ASDoc @eventType tag does not exist in ShockScript.
/** Some event */
[Event(name="act", type="Event")]
/** Some class */
class A extends EventTarget {}
Note: The
@eventtag introduced in ShockScript is used for documenting events better in reactive systems that use record types rather than classes for component parameters.
Embedding
Embedding files is much easier in ShockScript. The following returns typically an app:// URI for a file that will be automatically added to the application’s installation directory.
trace(Embed("flower.webp"));
Note: Implementations may support interpolating an artifact directory at the
Embedpath, such as{target}.trace(Embed("{target}/auto.generated.bin"));This is useful for when a build script generates a file at an artifact directory.
For static embedding, use a type= option:
Embed("data.txt", type="text/plain") // string
Embed("data.bin", type="application/octet-stream") // ByteArray
Variable shadowing
In ShockScript the following is valid in an activation:
var m:* = complexCentral.manager;
// more code...
var m = Manager(m);
Switch fallthroughs
The switch statement does not support fallthroughs, which helps preventing logical bugs by not requiring the break statement.
switch (v) {
case 0:
trace("zero");
case 1:
trace("one");
default:
trace("rest");
}
Switch type
The switch type statement allows for type and pattern matching:
switch type (v) {
case (d : Date) {
}
default {
}
}
switch type (exp) {
case (Plus(x, y)) {
}
case (Nothing()) {
}
}
Typed meta-data
Meta-data are typed on a surface-level to avoid typos. For custom meta-data, the convention is to define an annotations.* subpackage for a project, which contains Annotation classes, and use the import@ pragma, as shown in Meta-data annotations.
JavaScript
ShockScript gets too many roots from JavaScript, although more particularly linked to a previously abandoned version, JavaScript 2 (or ECMAScript 4th).
Map data type
The ShockScript’s Map data type differs fundamentally from JavaScript’s Map in that key-value pairs are accessed more naturally. ShockScript resolves the ambiguity between pairs and the prototype by differentiating property read and call.
m.x = 10
m.length()
Note: For a dynamic user class, it may very rarely be necessary to access a fixed variable rather than an arbitrary key-value pair; for that, the user may use a fixed expression as in
<?fixed={o.x}?>. This is mostly useless since ShockScript’s lexical resolution ignores dynamic names and most dynamic user classes access their internal variables from the same class block.
Variable shadowing
In ShockScript the following is valid in an activation:
var m:* = complex.manager;
var m = Manager(m);
“not” keyword
not may be used to negate in or is operators:
e not in a
v is not T
“this” binding
The this binding is fixed and present only in instance methods.
- Methods like
[object Function].apply()do not take athisbinding: only the parameters. - Instance methods are bound.
Java
Package flexibility
While importing definitions, the user can alias a definition, or even a package.
Chart.sx
package zero.chart {
public class Chart {
}
}
ChartType.sx
package zero.chart {
public enum ChartType {
const BAR;
const FLOW;
}
}
Consumer
import cn = zero.chart.*;
//
const chartType : cn::ChartType = "flow";
//
const chart = new cn::Chart(chartType);
E4X
ShockScript implements a modified version of the ECMA-357 2nd edition (E4X) standard, which facilitates XML processing and manipulation.
XML literals
By default XML literals evaluate to one of the XML and XMLList types.
package zero.information.generator {
public function retrieve(a : string) : XML {
return (
<information>{a}</information>
);
}
}
Note: XML literals ignore beginning and end whitespace on character sequence tokens regardless of the active XMLContext. Interpolation or the XML or XMLList constructors may be used instead where applicable with a XMLContext whose
ignoreWhitespaceoption is set to false.
Depending on the inference type, XML literals may be used for constructing implementation-defined objects, which apply implementation-specific verification rules.
package zero {
import whack.ds.UIComponent;
import s = spark.components.*
public class Main extends UIComponent {
public function Main() {
super()
final = (
<s:Application>
<s:Label>Hello World!</s:Label>
</s:Application>
)
}
}
}
Attributes
<t a/> is equivalent to <t a={true}/>. Accessing XML attributes can be directly done by the @ operator, as in xnode.@x.
Event handlers
Inline event handlers may be expressed as eventName&={statementList} as a shortcut to eventName={function(event){statementList}}, as in:
<button click&={trace("clicked!")}>Click me</button>
If the event has no parameters, then the attribute above is equivalent to eventName={function(){statementList}}.
Interpolation
<div {rest}>
{undefined}
{null}
{node}
{node_list}
{plain_text}
{number} <!-- The Number union -->
{boolean}
</div>
Filtering
XML and XMLList implement the filter operator.
people.(*.@name == "John")
Note: Unlike E4X 2nd, ShockScript does not clutter the lexical scope; the test variable is a wildcard * binding.
Descendants
XML and XMLList implement the descendants operator.
xnode..tag
Lexical contexts
The default xml namespace statement sets the default Namespace used in XML literals and name lookups during runtime.
default xml namespace = n
Note*: Unlike E4X 2nd, ShockScript makes the default xml namespace statement block-scoped, and not necessarily activation-scoped.
In addition, instead of E4X 2nd XML settings self-attached to the XML class, ShockScript includes an use xml pragma that acts similar to default xml namespace, but used for specifying a XMLContext object.
use xml ctx
Whack DS
Whack DS takes inspiration from both ReactJS and Adobe MXML for GUI dev.
Memoization
Whack DS memoizes components.
- It invokes
generic::cloneto clone an object and keep it as a previous state. - It invokes
equalsto compare two objects.
Style sheets
Linking style sheets is easy:
<div>
<fx:Style>
<![CDATA[
root {
background: red
}
]]>
</fx:Style>
</div>
A basic component
Here is a little code snippet:
package zero {
import whack.ds.UIComponent;
public class Box extends UIComponent {
var props : Props
public function Box(props : Props) {
super()
this.props = props
final = (
<></>
)
}
public type Props = tap {}
}
}
import z = zero.*;
var xn:whack.ds.Node;
xn = <z:Box/>
Monotonic counter
package {
import whack.ds.UIComponent;
import whack.util.*
public class Clock extends UIComponent {
var props : Props ;
[State]
var mSecs : bigint? = null;
public function Clock(props : Props) {
super()
this.props = props
whack.ds.useEffect(function() {
const itrv = setInterval(function() {
secs++
}, 1_000)
return function() {
clearInterval(itrv)
}
})
final = (
<span>{secs}</span>
)
}
public type Props = tap { start : bigint }
private function get secs() : bigint ( mSecs ?? props.start )
private function set secs(val) { mSecs = val }
}
}
Immutability
Ensure you follow immutability principles with States, Contexts and Props.
Note: ReactJS, Adobe Flex and many other technologies also present the same limitation. Since Whack DS uses always imposes references for States, Contexts and Props, not following these principles may lead to internal bugs.
Deriveds
[State] var x : decimal = 0;
[Bindable] var y : decimal = 0;
private function get z() : decimal (x + y)
Understanding Bindables
A Bindable may be read as “a variable associated with the surrounding component that does not trigger re-render on write”, and is frequently used for purposes like cache and obtaining DOM elements for manipulation, as opposed to States.
Under the hood, a Bindable variable is represented as a BindableReference.<T> instance, which must not be mistaken as the Bindable.<T> type that is typically:
public type Bindable.<T> = (
BindableReference.<T>,
function(T):void,
);
The function case allows tag attributes such as bind to specify a receiver that executes code, as in:
package zero.components {
import whack.ds.UIComponent;
import org.w3.web.Div;
public class Binding extends UIComponent {
[Bindable]
var element : Div? = null;
public function Binding(props : Props) {
super();
final = (
<div
bind={function($element) {
element = $element
if (props.bind is Function) {
Function(props.bind)($element)
} else if (props.bind) {
whack.ds.BindableReference.<Div>(props.bind).value = $element
}
}}>
<!-- Element content -->
</div>
);
}
public type Props = tap {
bind? : whack.ds.Bindable.<?Div>,
};
}
}
A Bindable annotatated variable may be assigned, in addition to its expected value type, a compatible whack.ds.BindableReference.<T>.
Understading Contexts
A Context annotatated variable may be read as “a variable associated to the surrounding component that uses a context provided by a parent component, which triggers a re-render when the parent assigns a different value for that context”.
Context annotatated variables are represented as ContextReference.<T> instances.
Those may be not be reused from other components.
Understanding States
A State annotatated variable may be read as “a variable associated to the surrounding component that triggers a re-render when it is assigned a different value”.
State annotatated variables are represented as State.<T> instances.
A State annotatated variable may be assigned, in addition to its expected value type, a compatible whack.ds.State.<T>.
Aliases
A component may be an alias by using the Alias meta-data, which specifies a ShockScript qualified identifier which is treated like a tag name.
package spark.components {
import whack.ds.UIComponent;
[Alias("w::VGroup")]
public class VGroup extends UIComponent {
}
}
The Alias meta-data is verified in such a way only for classes that extend whack.ds.UIComponent.
EyeExp
Whack’s approach to logotypes and icons is called the EyeExp feature, which uses dynamic icon names rather than enum, as well as namespace prefixes to prevent collision between libraries.
A component library typically provides an Application component so you do not have to specify its EyeExp namespace prefix explicitly.
package {
import whack.ds.UIComponent;
import s = spark.components.*;
public class Main extends UIComponent {
public function Main() {
super()
final = (
<s:Application>
<w:EyeExp name="camera" size={37}/>
</s:Application>
)
}
}
}
Monochrome icons are filled with the current CSS color.
Tips
- Remember that user-defined hooks do not take props as actual components do. If an user-defined hook returns any result, it should typically be either a
StateorBindableReferencewhich can be assigned to a State or Bindable annotatated variable.
MXML like constructs
This section describes E4X syntactic constructs applied as Whack DS nodes.
Intrinsic elements
Intrinsic elements belong to the implicit w namespace, such as <w:VGroup>. Compiler special elements belong to the implicit fx namespace for an Adobe MXML feel, such as <fx:Style>.
These prefixes may be shadowed by user definitions; in such cases, to use these, the user may define them with any lexical name as follows:
namespace w = "http://www.sweaxizone.com/2015/whack";
namespace fx = "http://www.sweaxizone.com/2015/whack/fx";
The user may do this short however:
namespace w = SX::w;
namespace fx = SX::fx;
Tag meta-data
Meta-data (like meta:x, or, in CSS selectors, [meta|x]) may be set mainly over native DOM tags, serving as meta-data. For a component to support these, it is required to define a metadata? : Map.<string, string> prop.
Using a Whack DS bindable, the attribute would be accessed as bindable!.@x.
For Whack Red, meta-data under the hood map to data- prefixed attributes, which can be seen when inspecting the elements in the browser console.
“key” attribute
The key attribute is reserved for uniquely identifying interpolated collection items.
Style sheets
<fx:Style> tags are used for linking style sheets to the parent tag and passing properties to the style sheet (which are referred by the style sheet as Property(color)).
package zero.components {
import whack.ds.UIComponent;
public class Ark extends UIComponent {
public function Ark() {
super();
final = (
<div>
<fx:Style color="yellow">
<![CDATA[
root {
color: Property(color)
}
]]>
</fx:Style>
click me
</div>
);
}
}
}
If the style sheet is too large, it may be moved out of the ShockScript file; for instance:
Ark.sx
package zero.components {
import whack.ds.UIComponent;
public class Ark extends UIComponent {
public function Ark() {
super();
final = (
<div>
<fx:Style source="Ark.css"
color="yellow" />
click me
</div>
);
}
}
}
Ark.css
root {
color: Property(color);
}
Style blocks can be conditional, as in:
<fx:Style if={condition}>
...
</fx:Style>
An arbitrary map of properties (Map.<string, *>) may be passed as well:
<fx:Style {map}>
...
</fx:Style>
Objects as style sheet properties
Takeaway: Whatever you pass to a style sheet is reactive together with the component’s final assignment (States, Props and Contexts). A style sheet property passed as an object isn’t necessarily reactive if you’re explicitly constructing a whack.ds.StyleSheet instance yourself and have not passed it to a final whack.ds.Node creation directly for a component’s evaluation.
Note: Follow immutability principles when it comes to passing and using properties in such a way with CSS.
The Property(...) property supports very simple operators without whitespace, like dot (.x, .q::x (relies on the @namespace CSS declarations)) and brackets ([0], ["x"], ['x']).
.skinMe {
color: Property(theme.colors.foreground);
}
@namespace Ark "http://www.zero.com/ark";
.skinMe {
background: Property(character.Ark::yay);
}
Linking style sheets in custom components
For a component to support <fx:Style> tags, it simply needs to support a stylesheet? : [whack.ds.StyleSheet] prop.
package zero.components {
import whack.ds.UIComponent;
public class Ark extends UIComponent {
public function Ark(props : Props) {
super();
final = (
<div>
<fx:Style extend={props.stylesheet}/>
click me
</div>
);
}
public type Props = tap {
stylesheet? : [ whack.ds.StyleSheet ],
};
}
}
The extend attribute may be used to include externally loaded styles as well.
Specifying inline styles
Use s:n={v} attributes as a shortcut to style={{ ..., n: v }}.
<button s:background="orange">Button</button>
Prop tags
Child tags may be used as parent-attached Props as long as they always use a prefix and it matches the parent’s prefix.
If not an interpolation, the child tag of a Prop tag is interpreted similarly to an object literal.
package {
import whack.ds.UIComponent;
import s = spark.components.*;
public class HelloWorld extends UIComponent {
public function HelloWorld() {
super();
final = (
<s:Group>
<s:layout>
<s:VerticalLayout gap={10}/>
</s:layout>
</s:Group>
);
}
}
}
The good, the bad
:( ⸻ Mutant
[State]
var x : [uint] = [];
x.push(10); // BAD
:) ⸻ Mutant
[State]
var x : [uint] = [];
x = [...x, 10] // GOOD
You can also implement deriveds.
:( ⸻ Functions
final = (
<cset:Evaluator
functions={[ function():uint(10)
, function():uint (0) ]}/>
) // BAD
Callback caching (whack.ds.useCallback) only occurs implicitly for Functions assigned to the whole attribute.
:( ⸻ Functions
final = (
<cset:Evaluator>
<cset:functions>
{[
function():uint (10), // BAD
function():uint (0), // BAD
]}
</cset:functions>
</cset:Evaluator>
)
Callback caching (whack.ds.useCallback) only occurs implicitly for Functions assigned to the whole attribute.
:( ⸻ Functions
final = (
<cset:Evaluator>
<cset:finish>
{function(){doIt()} /* BAD */}
</cset:finish>
</cset:Evaluator>
)
Callback caching (whack.ds.useCallback) only occurs implicitly for Functions assigned to the whole attribute when using a syntactic XML attribute.
:) ⸻ Functions
final = (
<cset:Evaluator
finish&={doIt()} />
) // GOOD
:) ⸻ Functions
final = (
<cset:Evaluator
finish={function(e){doIt()}} />
) // GOOD
:( ⸻ Props
class Box extends UIComponent {
function Box({ x } : Props) {
super()
whack.ds.useEffect(function() {
if (x == 0) {
trace("zero!")
}
});
final = (
<></>
)
}
type Props = tap {
x : uint,
}
}
:) ⸻ Props
class Box extends UIComponent {
function Box(props : Props) {
super()
whack.ds.useEffect(function() {
if (props.x == 0) {
trace("zero!")
}
});
final = (
<></>
)
}
type Props = tap {
x : uint,
}
}
:) ⸻ Evaluation order
class Box extends UIComponent {
[Bindable]
var outside : uint;
function Box(props : Props) {
// 1 - super
super()
// 2 - variable initials & custom hooks
outside = props.outside;
...
// 3 - effects & custom hooks
...
// 4 - final
...
final = (
<></>
)
}
type Props = tap {
outside : whack.ds.BindableReference.<uint>,
}
}
Technical
Memoization
Whack DS automatically memoizes components, allowing for user customizable Prop/State equality comparison and clones through overriding the equals method and implementing a clone method.
Memoization allows to skip re-rendering a component when its Props do not change.
Just like with ReactJS, memoizing components has drawbacks such as possibly volatile code regions (such as when internationalizing a product with locale-specific translation strings). In such cases, relying on a Whack DS context will re-render the component when the context changes regardless of whether props did or not change.
Whack DS skips re-rendering component if the parent re-renders and the props are equals to the previous render; the Whack DS component’s own states updating with a different value will always re-render it.
Whack DS implementation stores previous state or previous properties by performing a generic::clone. For using custom classes inside states or Properties — like when a tuple, record, Array or Map is not enough — you may need a clone method that returns an object of the same kind and perhaps an equals method.
- Custom classes do not need a
clonemethod if they are, say, purely data with an optional constructor. - Custom classes whose instances should be references (that is, cloned by reference and equal by reference) should implement a
clonemethod that returns the this receiver as is and anequalsmethod that does simply===.
Callback caching
Whack DS caches callbacks (either lambdas, inline event handlers, instance methods of the same component or Functions declared inside the constructor) within applicable E4X attributes, since they are naturally ever changing Function objects regardless of whether they are lambdas or fixtures ─ for example, since they capture locals, this or ShockScript lexical contexts, they tend to return different Function objects ─ and this is crucial for memoization.
Note: Whack currently does not cache callbacks nested in objects. It is not recommended to use lambdas or ever changing Functions inside Prop objects within an E4X literal, as Whack will not give an error or warning for now.
For the tag-form of setting a Prop in an E4X literal (as in
<s:f>{function(){}}</s:f>), we have not considered caching either, since this is not very common; although that is easy to support in the future as well.
If a callback appears within a nested block, Whack tries contributing it as a whack.ds.useCallback to the component main evaluation’s body.
Whack DS doesn’t attempt to cache such a callback if it it does not belong to a component’s constructor or instance method. If it does belong to a constructor, the callback is cached; but after IR generation, if there is a chance of the constructor exiting execution before the generated whack.ds.useCallback callback, the compiler generates an error at the respective tag’s attribute.
Auto dependency tracking
- Whack DS presents extra overhead for State/Context/Prop accessors, so that, say, the surrounding effect or callback is said to be dependent on an used State/Context/Prop.
- Subsequent renders may still accumulate more dependencies, like conditional ones.
- Whack DS E4X attributes assigned to functions or methods from the same component are cached based on dependency tracking; same for E4X event handlers &=.
Props tracking
Whack DS automatically tracks not only states and context dependencies in an effect or callback, but also props.
What Whack DS does internally:
- The Props object is reused across renders. For every render, its internal hash map is cleared and then overwritten.
- tap {} types, which are used for representing props, desugar into classes which use a hash map internally for storing only props that are specified. Each prop gets its own getter, which detects surrounding effect or callback and returns the current value of the prop at the internal hash map.
- Track prop name for comparison + previous value for the surrounding effect/callback if any
Component validation
The following apply when using E4X literals to construct whack.ds.Node.
- A tag name must resolve to either
- A component class that extends
whack.ds.UIComponent- May be an Alias itself
- A Prop (clarified in the subsections)
- A context provider
- An intrinsic element
- A component class that extends
Class definitions that extend whack.ds.UIComponent are validated in a flat way to avoid programmer bugs:
- The
Aliasmeta-data - Every instance variable is either
- tap {} typed (at most one variable of this kind, which is usually the Props object)
- A
Bindableannotatated variable - A
Contextannotatated variable - A
Stateannotatated variable
- The class either omits the constructor, or defines a constructor whose signature is either
function():voidorfunction(Props):void, wherePropsmust be a tap {} type.
Tooling
Building and structuring projects should feel way like Cargo from the Rust language, at least in the Whack engine.
Configuration ease
Configuring ShockScript projects is way easier compared to other technologies.
Unlike NPM + TypeScript, you do not have to worry about transpilation or whatsoever when building libraries or applications; not even comparable as ShockScript targets WebAssembly. If you were implementing a library in NPM + TypeScript, you were forced to transpile TypeScript to JavaScript first due to the tsconfig.json file which is ignored from third-party dependencies while compiling, otherwise you would get inconsistent transpilation or compiler errors.
Whack case
API documentation
API documentation is automatically built for packages that are published to the Whack package registry.
Namespaces
ShockScript defines properties whose name is tied to a namespace, which is useful for version control and protection.
Helper.sx
package org.lazy.runner {
public class Helper {
private namespace FunInternal = "http://www.fun.com/2007/runner/internals";
/** @private */
FunInternal const cache : [double] = [];
//
public function foo() {
FunInternal::cache.push(0);
}
}
}
friend.sx
package org.lazy.runner.advanced {
import org.lazy.runner.*;
public function friend(helper:Helper) {
namespace FunInternal = "http://www.fun.com/2007/runner/internals";
helper.FunInternal::cache.push(10);
}
}
Namespaces additionally apply to record types.
Flexible.sx
package zero.hit {
public namespace Flexible = "http://www.zero.org/hit/Flexible";
}
Judgement.sx
package zero.hit {
public namespace Judgement = "http://www.zero.org/hit/Judgement";
}
Pair.sx
package zero.hit {
public type Pair = map {
Flexible::strength : [decimal],
Judgement::strength : [decimal],
};
}
Event model
The native EventTarget class is designated for dispatching and listening to events, and actual implementations may use it for implementing an hierarchic DOM event model.
In addition, the IEventTarget interface may be implemented instead of extending the EventTarget class.
Subclassing EventTarget
The following program demonstrates implementing a basic EventTarget subclass that is able to emit events:
/** My event */
[Event(name="act", type="Event")]
/** My Actor */
class Actor extends EventTarget {
function m():void {
this.emit(new Event("act"));
}
}
Listening to an event
Subscribing to an event looks as follows:
actor.on("act", function() { trace("acting") });
Implementing an event class
Event constructors must always take the event name (“type”) as the first argument; any other arguments may follow. In the following code the user inherits the Event constructor.
class UserEvent extends Event {}
EventTarget implementation
It is a rare case for the user to need to implement their own EventTarget class: it may only arise if the user needs EventTarget to work with their own Document Object Model.
The “emit” method
The emit method is defined as follows:
[Limit("E extends Event*(this)")]
/**
* Dispatches an event.
*/
function emit.<E>(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.
The “on” method
The on method is roughly defined as follows:
[Limit("E extends Event(this)")]
/**
* Registers an event listener.
*/
function on.<E>(type:E.name, listener:function(E.type):void) : void {
//
}
The third parameter was omitted for clarity.
Conditional compilation
__ns__::constant
__ns__::constant {
//
}
__ns__::constant var x
Iteration
ShockScript features full object-oriented iteration.
coconuts.length()
scores.some(function({score}) score > 0)
people.(*.age >= 18)
The user may override the key and value iterators by implementing the Iterable.<K, V> interface.
class A implements Iterable.<string, double> {
public function keys():string {
for (var i = 0; i < 10; i++) {
yield i.toString();
}
}
public function values():double {
for (var i = 0; i < 10; i++) {
yield i;
}
}
}
Environment variables
Environment variables may be read from the project’s .env file using the Env::VAR_NAME expression:
Env::SECRET
Implementations may include predefined variables when using this expression.
Type matching
“is” operator
v is T
“switch type” statement
switch type (v) {
case (d : Date) {
}
default {
}
}
switch type also works on algebraic data types:
switch type (exp) {
case (Plus(10, right)) {
}
}
“if let” statement
if (let Plus(10, right) = exp) {
}
Type inference
ShockScript’s type inference presents some particular cases.
Simple enumerations
x = "variant"
f = ["hotfix", "lazy"]
f = { hotfix: true, lazy: true }
Algebraic enumerations
x = Variant()
Unicode Code Points
You can assign a string literal to an uint, int, byte or Number as long as it contains exactly one character, resulting into an Unicode Code Point value.
ch = "A"
// equality
ch == "A"
Other object initialisers
Object initialisers are also applicable to:
Map.<k, v>Set.<t>map { }typestap { }types- Certain classes
var ctx:Context
ctx = { duck: 10 }
trace(ctx.inspire())
class Context {
var duck : uint = 0
var cool : boolean = false
public function inspire():uint (
Math.random(0, duck + (cool ? 5 : 0))
)
}
The rest operator has its own rules for each applicable type, so as to avoid programmer bugs.
Other array literals
Array literals are also applicable to:
Set.<T>
Where the inference type isn’t applicable
There may be dynamic spots where compile-time inference is not possible, unless using a syntactic construct like a variable definition containing a type annotation. If you need inline type inference, consider:
10 // double
10d // double
10i // int
10u // uint
10f // float
10m // decimal. "m" for money
10n // bigint
t(v)
[] : [t]
Note that:
- Not all
Numberdata types have a suffix available. t(v)may be equivalent to av as! tcast, except thattis verified beforevandvis verified withtas the inference typet(v)is not necessarily a cast for certain classes that define a class-attachedmeta::invokemeta-method.t(v)is a cast at least for enumerations, structural types (like unions, tuples, records and functions), primitive types and most global objects.t(v)is definitely not a cast for the classes comprising algebraic enumeration variants.
Clone
generic::clone performs a common clone.
o.generic::clone()
You may customize it for a class with:
public function clone(deep:boolean = true):c {
}
// or
public function clone():c {
}
Any parameters are allowed as long as they are optional; however if the first one is a Boolean, it is understood as the deep parameter.
Serial
ShockScript includes a facility related to the Serde framework from the Rust language, used for serialization and deserialization of user data types.
The ShockScript compiler does auto-generate internal overrides for supporting this facility efficiently.
Namespace methods
A class that implements the self-attached meta::invoke meta-method may act as a namespace method.
package zero.fn {
public class f {
meta function invoke(options:Options? = null):double (
n
)
public type Options = map {
}
}
}
Scope
This not so formal document specifies the syntax, semantics, execution and global objects of the ShockScript language.
Definitions
Code Point
A Code Point as specified by the Unicode standard.
Scalar Value
A Scalar Value as specified by the Unicode standard.
Required function
A function that contains at least one required parameter.
Required method
A method that contains at least one required parameter.
Required constructor
A constructor that contains at least one required parameter.
Notational conventions
Syntactic and lexical grammars
This document uses the following notation to define one or more productions of a nonterminal of the syntactic grammar:
-
Symbol :
-
Production1 Symbol1
ProductionN
This document uses double colon (::) notation to define productions of a nonterminal of the lexical grammar:
-
Symbol ::
-
terminal
The opt subscript is used to indicate that a nonterminal symbol is optional.
-
Symbol ::
-
Symbol1opt
A bracketed clause or predicate may appear between the rules of a production, such as in:
-
Symbol ::
-
[lookahead ∈ { 0 }] Symbol1
[lookahead ∉ { default }] Symbol2
[lookahead ≠ xml ] Symbol3
SourceCharacters [but no embedded <![CDATA[]
The «empty» clause is matched by the grammar where other rules do not match otherwise.
-
Symbol :
-
«empty»
Braces subscripts are used to quantify a rule:
- Symbol{4} — Four of Symbol
- Symbol{2,} — At least two of Symbol
- Symbol{1,4} — One to four of Symbol
Types
This section describes the data types and certain type expressions available in ShockScript.
Wildcard type
The * type is dynamically typed and consists of all possible values in other types.
void type
The void type consists of the undefined value.
null type
The null type consists of the null value.
String type
The String type (or its alias string) represents an UTF-8 encoded character sequence.
Note: the
.lengthproperty of astringequals the byte total, and the.chars().length()method of astringequals the Unicode code point total.
Boolean type
The Boolean type (or its alias boolean) consists of the values false and true.
Number type
The Number type is a (byte, short, int, uint, long, bigint, float, double, decimal) union. The language allows possibly mixed relational, arithmetic and bitwise operations with Number, forcing a conversion on the involved operands if necessary.
float type
The float type represents an IEEE 754 single-precision floating point.
double type
The double type represents an IEEE 754 double-precision floating point.
decimal type
The decimal type represents an arbitrary range decimal number.
byte type
The byte type represents an unsigned 8-bit integer.
short type
The short type represents a signed 16-bit integer.
int type
The int type represents a signed 32-bit integer.
uint type
The uint type represents an unsigned 32-bit integer.
long type
The long type represents a signed 64-bit integer.
bigint type
The bigint type represents an arbitrary range integer.
Array type
The Array.<T> type, abbreviated [T], represents a growable list of elements.
Numeric optimization
[t] is optimized for when t is a Number or Boolean type; for instance, [uint] uses a growable buffer optimized specifically for 32-bit integers.
Map type
The Map.<k,v> type represents a hash map.
Note: Property access on a
Mapequals data access. Method call on aMapequals aMapmethod use.
Instance usage
const map = new Map.<string,double>();
map.x = 10;
map.y // ReferenceError.
// use "in" before
const fns = new Map.<string,Function>();
fns.m = function() 10;
(fns.m)() // 10
Set type
The Set.<t> type represents a hash set collection of elements.
Note: Property access on a
Setequals data access. Method call on aSetequals aSetmethod use.
Instance usage
var set : Set.<string>
set = [];
set.shock = true;
set.qux // false, no ReferenceError
Tuple types
Tuple types, of the form [t1,t2,tN], are immutable sequences consisting of known element types at compile time. A tuple type contains at least two elements.
[boolean,float]
Tuples are stored in an unpacked form internally wherever possible; therefore, accessing a tuple may box it into a new Object whenever requested. ShockScript does not intern tuple objects.
Function types
Structural function types inherit from the Function class, taking the form function(...) : t.
function(t, t=, ...[t]):t
A structural function type may specify:
- zero or more required parameters (
t) followed by - zero or more optional parameters (
t=) followed by - a rest parameter (
...[t]), - and a result type.
Record types
Record types {} are simple property records.
Specifically, there are variations of the record type, which are each represented and used in a different way.
map {} types are like a hash-map internally, using boxing for primitive types. Fields that are not specified are not inserted into the structure.
type Options = map {
quack? : uint,
shot? : boolean,
};
tap {} types are similar to map {}, but their creation and field accessors are implementation-defined, and they desugar to classes.
Although map {} and tap {} types appear as type expressions, they are unique; structurally-matching records cannot be assigned to the other, or vice versa.
Version control
Fields of a record type may be tied to a namespace, which is useful for version control.
Flexible.sx
package zero.hit {
public namespace Flexible = "http://www.zero.org/hit/Flexible";
}
Judgement.sx
package zero.hit {
public namespace Judgement = "http://www.zero.org/hit/Judgement";
}
Pair.sx
package zero.hit {
public type Information = map {
Flexible::strength : [decimal],
Judgement::strength : [decimal],
};
}
Field omission
A field is required to be initialized in object literals unless it contains undefined. A field such as x? : T is equivalent to x : (void, T).
A field containing null but not undefined must be initialized.
Field order
Due to sensitive field order, record types with equivalent fields but in different orders will be incompatible.
Writing ShockDoc comments
Fields may have a preceding ShockDoc comment, as in:
type R = map {
/**
* Comment.
*/
x : double,
};
Compatibility
Two record types are compatible only if either:
- One is used as a subset of another
- Fields are equivalent, appear in the same order and include no ShockDoc.
Rest
One trailing ...rest component may appear in a record, where rest must be another record type. The resulting type is a subtype of rest and properties must not collide.
// A
type A = map { x : double };
// B < A
type B = map { y : double, ...A };
“tap {}”
tap {} types desugar into classes dedicated to reactive systems like Whack DS, typically for representing UI component properties.
type Props = tap {
x? : double,
}
- In Whack DS, when the
xproperty of the abovePropstype is accessed, thexproperty is auto tracked as a dependency of the surrounding effect or callback. - A tap {} type in Whack DS uses a hash map for storing props internally, since components use to define several properties, including several event handlers, which are not always specified by the consumer.
Union types
The structural union type, written (t1,t2,tN), consists of two or more member types, containing all possible values of the member types.
(decimal,string)
Restrictions
- Unions contain two or more members.
Default value
The default value of an union type is determined as follows:
- If it contains
void, thenundefined. - If it contains
null, thennull. - No default value.
Nullability
The following shorthands are available for nullability:
- “
t?” is equivalent to(t,null). - “
t!” removesnulland/orvoidfromt.
Object type
All types but { void, null, uint, int, float, double, decimal, bigint, boolean, string } represent referenceable objects. The Object class is inherited by all types but { *, void, null, union }.
Note: When it is necessary to obtain the constructor of an object, use:
obj.meta::class()
Conversions
This section describes which type conversions are available.
Casts may occur as either t(v) (strict conversion) or v as t (optional conversion). The behavior of the call operator over a type may not always be a conversion depending on if t implements the self-attached meta::invoke() meta-method.
v as t // returns t?. failure returns null
v as! t // failure throws a TypeError (as-strict)
t(v) // same as "v as! t"
Constant coercions
Constant coercions occur implicitly both at compile-time and runtime, converting a constant into another constant.
| Kind | Result |
|---|---|
undefined to flag enumeration | Interned instance whose value is zero (0). |
null to flag enumeration | Interned instance whose value is zero (0). |
undefined to t containing both undefined and null | undefined |
undefined to t containing undefined and no null | undefined |
undefined to t containing null and no undefined | null |
null to t containing undefined but not null | undefined |
null to t containing null but not undefined | null |
null to t containing both undefined or null | null |
Numeric constant to * or Object | Equivalent constant of the target type. |
String constant to * or Object or union containing string | Equivalent constant of the target type. |
Boolean constant to * or Object or union containing boolean | Equivalent constant of the target type. |
Namespace constant to * or Object or union containing Namespace | Equivalent constant of the target type. |
Type constant to * or Object or union containing Class | Equivalent constant of the target type. |
| Numeric constant to another compatible numeric type | Numeric constant with value coerced to target type. |
| Numeric constant to union containing at least one compatible numeric type | Numeric constant of the target type containing value coerced to the containing numeric type, preferring the same numeric type or otherwise the first numeric type found. |
NaN to float | NaN |
NaN to decimal | NaN |
-Infinity to float | -Infinity |
+Infinity to float | +Infinity |
-Infinity to decimal | -Infinity |
+Infinity to decimal | +Infinity |
Implicit coercion
Implicit coercions occur implicitly both at compile-time and runtime, after trying a constant coercion.
| Kind | Result |
|---|---|
From * | |
To * | |
| From numeric type to compatible numeric type | |
| To covariant (includes base classes, same parameterized type (if current type arguments implicitly coerce to the target type arguments), implemented interfaces, unions and inherited record type) | |
| From union to compatible union | |
| From union member to union | |
| From union to a common base type | For example, given type U = (B, C), a var a:A = u; declaration is valid as long as B and C share A as a base type. |
| From structural function type to another compatible function type | |
From t type parameter to t? |
Note:
interfacetypes inheritObject.
Parameterized types
ShockScript allows implicit coercions from t.<...> to t.<...> where t is a parameterized type, where the final type contains type arguments which the original type’s type arguments implicitly coerce to.
Note: Implicitly coercing an Array, Map or Set type to use covariant element types is allowed; however overwriting the collection later with unexpected element values may throw a TypeError during runtime.
For other types, using an unexpected type somewhere in place of type parameters may lead to an internal error during runtime.
Casts
Casts occur when resolving v as t or t(v), after trying an implicit coercion.
| Kind | Result |
|---|---|
To contravariant (from interface to interface subtype, from class to subclass, or record type subtype) | |
| To union member | |
From * or Object to interface | |
To a contravariant [t] type | A new Array filtering out incompatible elements. |
To a possibly incompatible Map.<K, V> type | A new Map filtering out incompatible fields. |
To a contravariant Set.<t> type | A new Set filtering out incompatible elements. |
| To same parameterized type if not Array, nor Map and nor Set and if current type arguments can cast to the target type arguments | E.g. c.<*> to c.<double> |
string to enumeration | Identification of an enumeration variant by its string name. |
| Number to enumeration (using the same numeric type) | For regular enumerations, identifies a variant by its numeric value. For flag enumerations, identifies variant bits. |
To string | For undefined, returns "undefined"; for null, returns "null"; for other types, invokes toString(). |
To boolean | Evaluates truthy value. |
To double | Forced conversion to double-precision floating point. |
To float | Forced conversion to single-precision floating point. |
To decimal | Forced conversion to 128-bit decimal number. |
To byte | Forced conversion to 8-bit unsigned integer. |
To short | Forced conversion to 16-bit signed integer. |
To int | Forced conversion to 32-bit signed integer. |
To uint | Forced conversion to 32-bit unsigned uninteger. |
To long | Forced conversion to 64-bit signed integer. |
To bigint | Forced conversion to an arbitrary range integer. |
| Record type into equivalent record type of non-uniform field order | |
| From type parameter |
Note:
interfacetypes inheritObject.
Parameterized types
ShockScript allows casts from t.<...> to t.<...> where t is a parameterized type, where the final type contains type arguments which the original type’s type arguments may cast to.
Note: These conversions are always safe for Array, Map and Set types, as they create new objects.
For other types, the resulting object is the same, therefore unexpected type or property related errors can occur during runtimes.
Property lookup
LookupKey
LookupKey is either LocalName(name) or Computed(value).
LookupKey.Value
LookupKey.Value returns:
- For LocalName(name), a StringConstant equivalent to name or defer if
stringis unresolved. - For Computed(value), value.
LookupKey.Type
LookupKey.Type returns:
- For LocalName(name), the
stringtype or defer. - For Computed(value), the static type of value or defer.
LookupKey.Number
LookupKey.Number returns:
- For LocalName(name), undefined.
- For Computed(value), value is NumberConstant(v) ? v : undefined.
PropertyLookup()
PropertyLookup(base, openNsSet, qual, key as LookupKey, followedByCall as boolean, fixed as boolean) takes the following steps in order, where fixed allows forcing access to a fixture property on dynamic types (used by the <?fixed={}?> expression):
- If base is invalidation
- Return invalidation
- Let localName = key is LocalName(name) ? name : undefined
- Let numberKey = key.Number
- If base is a TypeConstant(type)
- base = type
- Else if base is a FixtureReferenceValue and base.Property is a type
- base = base.Property
- If base is a class
- If localName is undefined or (qual is specified and qual is not a namespace nor a NamespaceConstant)
- Return StaticDynamicReferenceValue(base, qual, k.Value)
- For each descending class in base hierarchy
- Defer if class is unresolved.
- Let r = GetQNameInNsSetOrAnyPublicNs(class static properties, openNsSet, qual, localName)
- If r != undefined
- Mark r as used.
- Let r = r.ResolveAlias()
- Defer if r property’s static type is unresolved.
- Return r.Wrap()
- Return undefined
- If localName is undefined or (qual is specified and qual is not a namespace nor a NamespaceConstant)
- If base is an interface
- Return undefined
- If base is a value
- Let baseType = static type of base or defer
- If baseType is invalid
- Return invalid
- baseType = baseType.ResolveAlias()
- If (followedByCall == false and fixed == false) and baseType defines an instance method
meta::get(possibly a multi method)- Let foundRegularProperty = false
- For each
meta::get(k:K):Vmethod- If qual != undefined
- If K ==
*or K == (Objector defer) or K == (QNameor defer) or K ?union contains (QNameor defer)- Return KeyValuePairReferenceValue(base, meta-method, qual as a
Namespaceobject, key.Value coerced to (stringor defer))
- Return KeyValuePairReferenceValue(base, meta-method, qual as a
- Continue loop
- If K ==
- If key.Value is a (
stringor defer) value and (K == (QNameor defer) or (K ?union does not containstringand K union containsQName))- Return KeyValuePairReferenceValue(base, meta-method, undefined, key.Value)
- If K ==
*or K == (Objector defer) or K == (stringor defer) or K == (QNameor defer) or (K ?union containsstringor K union containsQName)- foundRegularProperty = true
- If (static type of key.Value or defer) fails on implicit coercion to K
- Continue loop
- If K is a
Numbermember and (static type of key.Value or defer) is aNumbermember- Return KeyValuePairReferenceValue(base, meta-method, undefined, forced conversion of key.Value to K)
- Return KeyValuePairReferenceValue(base, meta-method, undefined, key.Value implicitly coerced to K)
- If qual != undefined
- If (static type of key.Value or defer) != (
stringor defer) or foundRegularProperty- Throw a verify error
- Let hasKnownNs = qual == undefined or (qual is a namespace or NamespaceConstant)
- If localName == undefined
- If numberKey != undefined and baseType is a tuple
- Let i = numberKey coercion to
uint - If i < 0 or i >= baseType.ElementTypes.Length
- Throw a verify error
- Return TupleReferenceValue(base, i)
- Let i = numberKey coercion to
- Return DynamicReferenceValue(base, qual, key.Value, followedByCall, fixed)
- If numberKey != undefined and baseType is a tuple
- If baseType ==
*- Return DynamicReferenceValue(base, qual, key.Value, followedByCall, fixed)
- If baseType is a class
- For each descending class in baseType hierarchy
- Defer if class is unresolved
- Let prop = GetQNameInNsSetOrAnyPublicNs(class prototype properties, openNsSet, qual, localName)
- If prop != undefined
- Mark prop as used
- prop = prop.ResolveAlias()
- Call prop.Defer() (if about to defer, implementation may report the cause as unresolved expression in a location)
- If prop is a namespace or NamespaceConstant
- Return NamespaceConstant(prop) if prop is a namespace, or otherwise prop as is
- Return InstanceReferenceValue(base, prop)
- For implemented interfaces of baseType
- Lookup method (step required for optional methods)
- For each descending class in baseType hierarchy
- Else if baseType is an interface
- For each descending itrfc in baseType hierarchy
- Defer if itrfc is unresolved
- Let prop = GetQNameInNsSetOrAnyPublicNs(itrfc prototype properties, openNsSet, qual, localName)
- If prop != undefined
- Mark prop as used
- prop = prop.ResolveAlias()
- Call prop.Defer() (if about to defer, implementation may report the cause as unresolved expression in a location)
- Return InstanceReferenceValue(base, prop)
- Lookup for the
Objectinstance definitions
- For each descending itrfc in baseType hierarchy
- Return undefined.
- If base is a
package- If localName is undefined or (qual is specified and qual is not a namespace nor a NamespaceConstant)
- Throw a verify error
- Let r = undefined
- Let prop = GetQNameInNsSetOrAnyPublicNs(base properties, openNsSet, qual, localName)
- If prop != undefined
- Mark r as used.
- prop = prop.ResolveAlias()
- Call prop.Defer() (if about to defer, implementation may report the cause as unresolved expression in a location)
- r = prop.Wrap()
- Return r
- If localName is undefined or (qual is specified and qual is not a namespace nor a NamespaceConstant)
- If base is a type parameter with a
Limit("... extends Event(...)")constraint- If localName is undefined or (qual is specified)
- Return undefined.
- If localName = name
- Return EventNameType(base type parameter).Wrap()
- If localName = type
- Return EventTypeType(a previously introduced EventNameType).Wrap().
- Return undefined.
- If localName is undefined or (qual is specified)
- Return undefined.
Note: entity.Wrap() wraps entities into values. For instance, wrapping a variable into a property reference, where it belongs to a package, will produce a PackageReferenceValue.
Note: entity.Defer() defers if an entity is unresolved or if a direct compound (like an alias resolution) is unresolved.
InScopeLookup()
InScopeLookup(scope, qual, key as LookupKey, followedByCall as boolean, fixed as boolean, contextType) takes the following steps in order:
Note: Content lacking.
Packages
A package consists of a name (typically a reverse domain name), a set of properties and two namespaces, public and internal.
A package com.business.enum is expressed as:
package com.business.enum {
//
}
Note: One common convention is for packages to use a prefix domain (one of (
com,net,org,me,goog)); alternatively an user may use a prefixless domain name (such asskiarather thangoog.skia). Themeprefix is used for personal content and the rest for companies, organizations and groups.
The user defines properties under the package inside the package block, as in:
package f.q {
public function f():void {
}
}
Top-level package
The top-level package, which defines global properties, is equivalent to:
package {
//
}
When a global name is shadowed, the user may use the SX namespace to lookup a global name:
Math
// or
SX::Math
For defining a custom alias to the top-level package, define a namespace with the URI http://www.sweaxizone.com/2015/shockscript/global, as in:
namespace SX = "http://www.sweaxizone.com/2015/shockscript/global";
Name shadowing
It is possible to fully qualify a name in an expression using a package and one of its items, shadowing any other variables.
import org.colourful.color.Color;
var com = 0;
trace( org.colourful.color.Color(0x10_00_00) );
trace( Color(0x10_00_00) );
ReadMe
A directory identifying a package relative to a source path may contain a README file (either README or README.md) written as Markdown text, which serves as a means to attach documentation to the package.
You may omit a package from the API reference by prepending a <!-- @private --> comment at the top of the ReadMe file.
Package single import
A package single import is contributed to the lexical scope for the following directive:
import f.q.x;
Package wildcard import
A package wildcard import is aliased for the following directive:
import q = com.business.quantum.*;
Then q may be used as a qualifier to resolve to a name in the com.business.quantum.* package (excluding subpackages).
q::x
For the following directive, the package wildcard import is contributed to the lexical scope:
import com.business.quantum.*;
Source path
Depending on where the ShockScript program is applied, the following apply:
- A package definition must contain exactly one definition item, and its name must match the source path.
- A source file must consist of exactly one package definition.
A script that is literally a script per se typically does not follow these rules.
Namespaces
Names are three-dimensional, consisting of a namespace (the qualifier) and a local name. The :: punctuator is used in qualified identifiers for using a namespace prefix and a local name, as in:
q::n
o.q::n
Namespaces appear as optional access modifiers in annotatable definitions, as in:
Personal var x : decimal = 0
There are reserved namespaces and user namespaces.
Reserved namespaces
Reserved namespaces (or system namespaces) are created implicitly by the language:
publicinternalprotectedprivatestatic protectedmeta
They are tied to a parent (such as a package, a class or a scope), except in the case of meta.
A namespace definition that omits the URI creates an internal namespace:
namespace Personal
User namespaces
User namespaces are identified by an URI.
namespace Personal = "http://www.personal.net/2007"
Classes
A class is an inheritable user-defined type that may be used to create objects.
class C1 {
//
}
const obj = new C1();
Inner namespaces
A class owns three namespaces:
privateprotectedstatic protected
protected and static protected are propagated to the block of subclasses.
ShockDoc comment
A class may be prefixed by a ShockDoc comment.
/** Comment */
class C1 {}
Meta-data
A class may have zero or more meta-data.
[M1]
[M2]
class C1 {}
Inheritance
class A {}
class B extends A {}
Member shadowing
Members from base classes must not be shadowed except for overriding methods.
class C1 {
function m() {}
}
class C2 extends C1 {
function m() {} // ERROR!
}
Default inheritance
By default a class, excluding Object, inherits Object. A class can extend at most one class.
Final classes
A final class may not be extended:
final class A {}
class B extends A {} // ERROR!
Implementing interfaces
A class may implement zero or more interfaces:
class C1 implements I1, I2 {
//
}
Constructor inheritance
If the constructor of a class is not explicitly defined, then it is based on the base class’s constructor, using the same signature and initializing the instance with default field values:
class A {
var x:double;
function A(x:double) {
this.x = x;
}
}
class B extends A {
var y:double = 10;
}
new B(0);
new B(); // ERROR!
Constructor
The constructor of a class is a special initialization method named as the class itself, as in:
class C1 {
function C1() {}
}
Super statement
The super statement is used to invoke the constructor from the base class from a subclass constructor.
A constructor must contain the super statement if a class is inherited which consists of a required constructor.
class A {
function A(x:double) {}
}
class B extends A {
function B() {
super(0);
}
}
Abstract classes
An abstract class may not be directly instantiated through the new operator, and may define abstract methods. Non abstract subclasses are allowed to be instantiated.
abstract class A {
abstract function m():void;
}
Static classes
A static class may not be instantiated or inherited, and by convention consists of static properties and methods.
static class MyNamespace {
static const VALUE:double = 10.5;
}
Events
The class, in convention when either extending EventTarget or implementing IEventTarget, may define possibly emitted events through using multiple Event meta-data.
/**
* Event.
*/
[Event(name="eventName", type="T")]
/**
* Target.
*/
class A extends EventTarget {}
Static properties
Definitions marked static that appear within the class block are part of the static properties of the class, which are accessed as C.n where C is the class and n the property name.
Instance properties
Definitions not marked static that appear within the class block are part of the prototype of the class, and are called instance properties.
Nested classes
ShockScript supports nested classes.
Note: The initial decision for supporting nested classes is that they allow reducing the number of ShockScript source files when a programmer attempts to express a type close to an algebraic data type consisting of many variants.
Nested classes, due to the scope, may access both protected and private members of the enclosing classes.
//
static class Outer {
//
class Inner {
//
}
}
Dynamic classes
A class is dynamic if it defines a meta::get meta-method whose key is possibly a string or QName object. Property read and overwrite are differentiated from method call.
Although useless, if one needs to force fixture access instead of dynamic access, they may use:
<?fixed={o.x}?>
This is mostly useless since most dynamic classes only need to access internal variables from their own class block and the lexical name resolution skips dynamic names.
Note: It’s best for dynamic classes to expose only methods rather than properties.
Simple enumerations
Simple enumerations — specifically those that are not Algebraic Data Types containing type X() definitions — are special classes consisting of zero or more variants.
enum Variant {
const VAR_ONE;
const VAR_TWO = "var_two";
const VAR_THREE = [2, "var_three"];
}
Note: Variable definitions within an
enumdefine static constants which are referred to as variants.
Final
Enumerations are final, so they cannot be extended.
Static
Enumerations are static, so they cannot be instantiated through the new operator.
Type inference
When the inference type in a string literal is an enumeration, the literal may identify a variant by its name.
var val : Variant = "var_one";
When the inference type in an array literal or object initializer is a flag enumeration, the literal may be used to identify multiple variants.
[Flags]
enum F { const A, B, C }
var m:F
m = ["a", "b", "c"]
m = { a: true, b: true, c: true }
Flag enumerations
Flag enumerations differ from regular enumerations by having instances being able to contain zero or more variants.
[Flags]
enum F { const A, B, C }
Flag enumerations may be assigned undefined, null or [] to indicate absence of variants.
When a flag enumeration’s numeric type is a floating point, the values are internally cast between a unsigned 32 bit integer and that floating point’s type; therefore their range may be less than what the floating point supports.
All variants
Obtain all variants of a flag enumeration by using the ** expression with the enumeration as the inference type:
var f:F = **;
Internation
Flag enumeration objects are interned so that flags may be compared correctly.
[Flags]
enum E { const A, B, C }
const obj:* = E(["a", "b"]);
trace(obj == E(["a", "b"]));
Customizing the numeric type
Enumerations use the uint type by default to represent the variant values. The user is allowed to change the type to another numeric type through using a meta-data named after that numeric type.
[decimal]
enum E1 {
const A, B, C;
}
Variant initializer
The initializer of a variant may be expressed in four different forms, or simply be omitted:
StringLiteral
NumericLiteral
[StringLiteral, NumericLiteral]
[NumericLiteral, StringLiteral]
The ArrayLiteral syntax is used to allow specifying both a string and a number.
Variant name
The variant name as declared by the const is determined as follows:
- Let r = empty string
- If the initializer does not contain a string literal
- Let orig = binding identifier name
- r = conversion of orig to lowercase.
- Else
- r = the value of the string literal at the initializer.
- If r is already used by another variant’s name
- Throw a verify error
- Return r
Variant value
The variant value as declared by the const is determined as follows:
- If the enumeration is a flag enumeration 2. Return DecideFlagValue()
- Return DecideValue()
DecideValue()
- Let r = zero
- If the initializer does not contain a numeric literal
- If there is no previous variant, return 0.
- Let k = previous variant’s value
- r = k + 1
- Else
- r = the value of the numeric literal at the initializer.
- If r is already used by another variant’s value
- Throw a verify error
- Return r
DecideFlagValue()
- Let r = zero
- If the initializer does not contain a numeric literal
- If there is no previous variant, return 1.
- Let k = previous variant’s value
- r = k * 2
- Else
- r = the value of the numeric literal at the initializer.
- If r is not one or a power of two
- Throw a verify error
- If r is already used by another variant’s value
- Throw a verify error
- Return r
Implicitly added methods
For all enumerations
valueOf()
override public function valueOf():t {
//
}
Returns the numeric value of the enumeration instance, where t is the numeric type.
toString()
override public function toString():string {
//
}
Returns the name of the enumeration instance. For a flag enumeration, returns the names of the enumeration instance delimited by comma (,) by ascending value order.
For flag enumerations
meta::has()
meta function has(v:E):boolean {
//
}
Returns a boolean indicating whether the instance contains the specified flags or not, where E is the enumeration itself.
This allows for f in e expressions.
with()
public function with(v:E):E {
//
}
Returns a new value containing the specified flags, where E is the enumeration itself.
without()
public function without(v:E):E {
//
}
Returns a new value removing the specified flags, where E is the enumeration itself.
toggled()
public function toggled(v:E):E {
//
}
Returns a new value toggling the specified flags, where E is the enumeration itself.
Customized methods
Enumerations support customized methods:
enum E {
const A, B, C;
function get isA() this == "a";
}
Enumerations are prohibited from using variable definitions for purposes other than defining variants.
Algebraic enumerations
Algebraic enumerations, as opposed to simple enumerations, contain type X() definitions instead of just const X definitions; they desugar to an one-level hierarchy of classes, with the enum being an abstract class, and with each variant being a class with a meta::invoke method (so you can specify either the defined signature, or an object literal).
Example
enum Exp {
/**
* @param left Left-hand side.
* @param right Right-hand side.
*/
type Plus(left : Exp, right : Exp)
type Number(value : decimal)
type Empty()
}
var exp : Exp
exp = Empty()
exp = Plus({ left: x, right: y })
exp = Plus(x, y)
switch type (exp) {
case (Number(value)) {
}
case (Plus(x, y)) {
}
case (Empty()) {
}
}
Type inference
var e:E = A()
var e:E = Subcategory.A()
v is A
v as Subcategory.A
Namespaces
The variants of an algebraic enum may be under namespaces for extra conciseness, by using dots in their name:
enum I {
type Loc.Get(index : uint)
}
With type inference, one would basically have:
var i : I = Loc.Get(idx)
Shared properties
When an algebraic enum defines an instance variable, the constructors of each variant expect that variable inside a plain object if the variable isn’t optional.
enum Fragment {
public const location:Location;
type Simple();
type Combined(left:Fragment, right:Fragment);
}
Fragment.Simple({ location: loc })
Sub-blocks
Each variant may have its own class block.
enum E {
type A() {
override public function get x() : double (10)
}
type B() {
override public function get x() : double (24)
}
public abstract function get x() : double
}
Variant parameter list
Variant parameter lists may have follow entirely method signatures, having required parameters, default parameters and a rest parameter.
type A(...rest : [decimal])
Interfaces
Interfaces are user defined, non opaque types that may be implemented by classes through their implements clause.
interface I {
//
function m() : void;
//
function get x() : double;
function set x(value);
}
interface Ia extends I {}
The interface block may only contain function definitions, including regular methods, getters and setters.
Basemost type
An interface is a subtype of Object, although compile-time property lookups do not inherit Object properties.
ShockDoc comment
An interface may be prefixed by a ShockDoc comment.
/** Comment */
interface I {}
Meta-data
An interface may have zero or more meta-data.
[M1]
[M2]
interface I {}
Inheritance
An interface may extend other interfaces through the extends clause.
interface I3 extends I1, I2 {}
Shadowing members
Members from base interfaces must not be shadowed.
interface I1 {
function m() {}
}
interface I2 extends I1 {
function m() {} // ERROR!
}
Required methods
When interface methods omit their body, they are classified as required methods.
interface I {
function m():void;
}
Provided methods
When interface methods contain a body, they are classified as provided methods.
interface I {
function m() {
//
}
}
Method annotations
As annotations, interface methods may have nothing but an access modifier that is allowed to be anything but a direct reserved namespace, as well as any meta-data.
interface I {
meta function get(key:string):string;
}
Events
The interface, in convention when implementing IEventTarget, may define possibly emitted events through using multiple Event meta-data.
/**
* Event.
*/
[Event(name="eventName", type="T")]
/**
* Target.
*/
interface I extends IEventTarget {}
Dynamic interfaces
An interface is dynamic if it defines a meta::get meta-method whose key is possibly a string or QName object. Property read and overwrite are differentiated from method call.
Note: It’s best for dynamic interfaces to expose only methods rather than properties.
Serial
The Serial facility allows serializing and deserializing complex types into data formats like JSON and XML. YAML and TOML are mostly compatible with JSON.
Note: The compiler generates efficient code for serialization and deserialization as
serial::fromJSON,serial::toJSON,serial::fromXMLandserial::toXMLoverrides.You may call these back from custom (de-)serializer implementations.
This facility requires annotatating classes with either the Serial or XS meta-data, otherwise a TypeError is thrown while serializing or deserializing.
Variants of an algebraic data type do not need to specify the Serial or XS meta-data if the ADT does that already.
The default behavior while deserializing into a class c other than primitive types and certain global classes, unless defining a self-attached fromJSON or fromXML method, is roughly:
- If c[[Constructor]].length == 0
- Let o = new c()
- Else
- Let o = Create a new instance of c without evaluating the constructor
- Let fields = Each o[k] field that is not configured with the
skip="true"option. - Assign each field of fields to the respective data document field with appropriate parsing, applying any configured rename.
- Return o
Simple enums, including Flags enums, are serialized and deserialized in a different way from algebraic enums.
The map {} type are handled in the base Object implementation.
JSON
How one serializes or deserializes into/from JSON using the Serial facility:
import js = sx.serial.json.*
js::parse(str, T)
js::parse(obj, T)
js::object(v)
js::stringify(v)
It is an error if while deserializing a required field (one whose type has no default value) is missing from the input.
stringify also accepts an options object for pretty formatting.
Rename
[Serial]
class U {
[Serial("short-if")]
public var shortIf:boolean
}
[Serial(tag="type")]
enum Item {
[Serial(
"flyout",
f="color=c",
f="backdropBlur=backdrop_blur",
)]
type Flyout(
color:uint,
backdropBlur:boolean,
);
}
- The rename excludes any base namespaces.
- For enumeration variant’s field renames, more than one assign is allowed, in case the user needs an assign in the name of the renamed field.
- For ADTs, if no rename is specified, the variant name is used (including any base namespaces inside the ADT delimited by dot) instead.
- For class-hierarchies used as variants, like nodes resulting from a parser, if no rename is specified, the class’s fully qualified name (excluding the package name) is used instead.
Skip
[Serial]
class U {
[Serial(skip="true")]
public var shortIf:boolean
}
[Serial(tag="type")]
enum Item {
[Serial(
"flyout",
f="!backdropBlur",
)]
type Flyout(
color:uint,
backdropBlur:boolean,
);
}
Tag
The Serial tag option allows specifying the property used to identify the kind of an ADT or inheritance-based class. If it is not specified, no property is used; instead, a wrapper plain object is used with the variant name, as in:
{
"left": {
"Plus": {
"left": {
"Number": 10
},
"right": {
"Number": 9
}
}
},
"right": {
"Number": 7
}
}
Classes as variants
Users may want inheritance-based class definitions rather than ADTs (algebraic data types). Those do naturally work.
Unions
When using union types, there is a small chance of conflict depending on the union members: if two have the same name, one is preferred over the other. This does not happen for most cases though, so we won’t bother much with that for now.
Custom implementation
A class may implement a self-attached fromJSON method and/or an instance toJSON method for manually controlling serialization or deserialization.
TOML
How one serializes or deserializes into/from TOML:
import toml = sx.serial.toml.*
toml::parse(str, T)
toml::parse(obj, T)
toml::object(v)
toml::stringify(v)
TOML is handled the same way as JSON. The stringifier decides if it’s best to desugar objects and arrays into sections based on complexity.
XML
How one serializes or deserializes into/from XML:
import xs = sx.serial.xml.*
xs::parse(str, T, options)
xs::parse(xn, T, options)
xs::parse(xlist, T, options)
xs::xml(v, options) // XML
xs::stringify(v, options)
Fields that are null or undefined are omitted while serializing.
The XS meta-data is used for custom configuration which differs slightly from Serial as used by JSON or TOML, since it may be desired to configure whether a field should be a tag or an attribute and declare namespace prefixes and use them.
Supported options:
- A
prefixesArray containingNamespaceobjects. It must be specified, otherwise an error is thrown. - Pretty-formatting options for
stringify.
The default xml namespace = ns statement influences serialization or deserialization.
Note: Lacking content.
Tag
For ADTs, tags representing variants are named either after the variant’s name (including any base namespaces inside the ADT delimited by dot), or based on the rename (excluding any base namespaces).
For class-hierarchies used as variants, like nodes resulting from a parser, tags are named either after the class’s fully qualified name (excluding the package name), or based on the rename (excluding any base namespaces).
Marking a field as a tag
A field is implicitly a tag. For using an attribute instead for a field of a primitive type or whose type implements fromXML and toXML methods that take or return a text node, that field may be marked as an attribute using attribute="true".
package {
[XS]
public class Person {
[XS(attribute="true")]
public var name:string;
}
}
Arrays
Arrays or tuples translate to roughly, always unpacked:
<value>x</value>
<value>y</value>
<value>z</value>
Document element
The root class for serialization or deserialization must have a configuration meta-data with at least [XS(docElement="true")], otherwise an error is thrown. It may also use a rename with an optional prefix, as in:
package {
[XS(docElement="true", "e:example")]
public class Document {
}
}
Rename
Renames are pretty much like when working with the Serial meta-data, except a prefix can be specified:
[XS("e:U")]
class U {
[XS("e:short-if")]
public var shortIf:boolean
}
[XS(tag="type")]
enum Item {
[XS(
"e:flyout",
f="color=e:c",
f="backdropBlur=e:backdrop_blur",
)]
type Flyout(
color:uint,
backdropBlur:boolean,
);
}
If a field contains no rename, its name is deduced from its data type (either its class name or its XS meta-data).
Prefixes
Prefixes must be given while serializing or deserializing, as that allows for more concise XS meta-data.
docStr = xs::stringify(obj, {
prefixes: [
new Namespace("e", "http://www.eve.org"),
],
})
Custom implementation
A class may implement a self-attached fromXML method and/or an instance toXML method for manually controlling serialization or deserialization. Both methods may take or return one of { XML, XMLList }.
Patching data
The best way to patch data while retaining formatting and structure is having:
- The original document text
- A modified object, obtained from parsing the original document text
The sx.serial.<data format>.* subpackages provide a function for serializing an object into the data format’s most adequate object:
sx.serial.json.object(o)sx.serial.toml.object(o)sx.serial.xml.xml(o)
A third-party library may be used for patching the data document, which will typically take at least (originalDoc text, modification object), figure out what has changed and return a new document text.
- Such libraries should omit null or undefined fields or sections only if they did not appear in the original document.
Variables
A variable may be read-only or writeable, and consists of a type.
var x = 0
const y = 10
// Equivalent to above
let x = 0
let const y = 10
ShockDoc comment
A ShockDoc comment can be applied to a variable.
/** Comment */
var x
Meta-data
A variable may have zero or more meta-data.
[M1]
[M2]
var x
Initializer
If the initializer of a variable is a constant, then the variable consists of a constant initializer.
var x = 0
Variables do not need to have an immediate initialiser, in which case they must be initially assigned later.
Local shadowing
Re-declaring a variable is allowed inside activation blocks.
var m:* = central.manager;
// more code...
var m = Manager(m);
This is typically used to declare a new variable with a different data type.
Virtual variables
Virtual variables consist of either:
- a getter and a setter (writable);
- a getter (read-only);
- a setter (write-only).
A virtual variable’s type is determined based on the getter or setter.
function get x():float 10;
function set x(val) {
//
}
ShockDoc comment
A virtual variable derives ShockDoc comments from its getter or setter.
/** Comment */
function get x():float 10;
Meta-data
A virtual variable collects meta-data from its getter or setter.
[M1]
[M2]
function get x():float 10;
Methods
A method is a function that may be invoked. An instance method is a method defined in a class, enum or interface block which is not marked static.
function m() {}
Getters and setters are methods belonging to a virtual variable:
function get x():decimal 10;
function set x(val) {}
Constructors are methods that implement initialization for a class instance, as in:
class A {
function A() {}
}
ShockDoc comment
A ShockDoc comment can be applied to a method.
/** Comment */
function m() {}
Meta-data
A method may have zero or more meta-data.
[M1]
[M2]
function m() {}
Final method
Instance methods may have a final modifier, indicating that they are not to overriden by subclasses.
class A {
final function m() {}
}
Abstract method
Instance methods may have an abstract modifier under an abstract class, indicating that they must be overriden by subclasses.
abstract class A {
abstract function m():void;
}
If a subclass is abstract, it does not need to override a method marked abstract in the super class.
Generators
A method is a generator if the yield operator appears at least once in the method’s body. A generator is a method that evaluates like an iterator, consumed in pauses of yield operators until it hits a return statement or the end of code. A generator returns a Generator.<T> object.
function g():double {
yield 100.5;
}
If a method uses both yield and await, it is considered an iterator of Promise, therefore returning Generator.<Promise.<T>>.
Asynchronous methods
A method is asynchronous if the await operator appears at least once in the method’s body. An asynchronous method returns a Promise.<T> object.
function f():void {
await otherF();
}
If a method uses both yield and await, it is considered an iterator of Promise, therefore returning Generator.<Promise.<T>>.
Multi-methods
By using a generic-annotated header method, a method may be defined more than once with varying signatures, turning into a multi-method. Signatures must differ by the parameter list and not just the result type.
A generic header method must consist of exactly an untyped rest parameter and must omit the result type. Its purpose is to declare that a method is a multi-method and may be re-defined multiple times in the same scope or in the same directly-enclosing class.
generic function f(...);
function f():decimal {
// code
}
function f(val:decimal):Chainable {
// code
}
Overriding
An instance method may override a method in a base class through using the override modifier:
override protected function m() {
//
}
Restrictions
- A getter must override a getter, and a setter must override a setter.
- For a multi method, the override shall match a specific signature.
- It is not allowed to introduce more signatures to a multi method in a base class.
- It is not allowed to override a regular function in a base class with a multi method.
Overriding rules
A method S may override a method B with the following rules:
- S must begin with the same list of parameters as that of B.
- If B does not contain a rest parameter
- S may include additional optional parameters and/or a rest parameter.
- S must have the same result type of B, or a subtype of the B result type.
The above overriding rules apply to non-multi-methods; for multi methods, the override signature must be exactly the same.
Bound methods
Instance methods are bound such that retrieving a method from an instance will return a method tied to the instance.
class A {
function m():A {
return this;
}
}
const o = new A;
const { m } = o
trace(m == o.m); // true
trace(o == m());
Aliases
Aliases are used in different places of the language:
import CT = com.business.coreRT.enum.ContactType;
import q = com.business.quantum.*;
type U = (decimal, string);
namespace special_version;
ShockDoc comment
An alias may be prefixed by a ShockDoc comment.
/** Comment */
type Params = map {
x : decimal,
}
Meta-data annotations
Meta-data are bracketed, entries of textual key-value pairs that may be attached to annotatable directives, that are typed on a surface level during compile-time, but are dynamic during runtime. Meta-data are not unique and may appear more than once, as well as their key-value pairs.
[M1]
class A {}
[M1(x="y", z="w")]
class A {}
[M1(y)]
class A {}
Keyless entries are a single identifier (equivalent to a string) or a string literal not accompanied by a key.
Typing meta-data
Meta-data are defined in a conventional annotations subpackage of a project, which defines classes containing the special Annotation meta-data.
package bridge.annotations {
[Annotation]
public class N {
public var n : string? = null;
[Key("Special::t")]
public var t : Enum = "fly";
}
// Used as [B::Max]
[Annotation(prefix="B")]
public class Max {
// Keyless entries
public var values : [string] = [];
}
[Annotation]
public class Y {
// Untyped key-value pairs
public var pairs : [[string?, string]] = [];
}
}
Then the import@ pragma may be used to import annotations from a package.
import@ bridge.annotations.*;
Note: Libraries can alias to annotations from other libraries by using, say,
typedefinitions.
Parameterized types
Classes, algebraic enumerations, interfaces, type aliases and functions may specify type parameters, turning into parameterized types. ShockScript implements parameterized types using polymorphism.
Note: Array, Map and Set data types have certain specializations in their runtime representation internally for more efficient memory usage.
Array, Map and Set are the only types that store the specified type arguments to ensure the collection is strictly valid during runtime.
Any parameterized type other than Array, Map and Set gets its type arguments fully erased and their type parameters are replaced by * during evaluation.
class A.<T> {
// code
}
enum E.<T> {
type A(v : T)
}
interface I.<T> {
// code
}
type Alias.<T> = (decimal,[T])
function f.<T>() : void {
// code
}
Type operations
v is Array(matches an Array of any underlying type)v is Map(matches a Map of any underlying K/V types)v is Set(matches a Set of any underlying K/V types)- The is/as/as-strict operators and
t(v)casts are implemented at runtime receiving an optional type arguments list, which are used for proper Array, Map or Set check. Involved type arguments may be*, in which case any type may be matched. - is/as/as-strict and
t(v)completely ignore type arguments for parameterized types other than Array, Map or Set.
Parameter constraints
Type parameters may be attached multiple constraints.
[Limit("T extends A, B")]
function f.<T>(o:T) {
//
}
[Limit(
"X extends Consumer.<Y>",
"Y extends Liquid",
)]
function f.<X, Y>(x:X, y:Y) {
//
}
[Limit("E extends Event(A)")]
function f.<E>(type:E.name, o:E.type) {
//
}
[Limit("E extends Event*(A)")]
function f.<E>(o:E) {
//
}
The Limit meta-data may appear at most once, specifying multiple constraint expressions as its entries.
Event constraints
Event constraints allow inspecting available events as defined by the Event meta-data in classes and interfaces, including the inherited events and events from the implemented interfaces.
Event constraints are allowed to take this as the base type, reflecting the current class’s events:
package com.business.coreRT.events {
/**
* Event dispatcher.
*/
public class EventTarget {
[Limit("E extends Event(this)")]
/**
* Dispatches an event.
*/
public function emit.<E>(e:E):boolean {
//
}
}
}
Event()yields the name-type pair of an event. The.typeproperty of the pair relies on previous introduction of the respective.namesomewhere.Event*()ensures event creation is correct by analyzing thenew E(type, ...)expression.
Note: The
Event()constraint contributes anamefield that yields thestringtype, but its purpose is for auto completion in integrated development environments.
Lexical scopes
Internal properties
| Name | Description |
|---|---|
| [[Parent]] | Optional parent scope. |
| [[OpenNamespaces]] | Open namespace list. |
| [[Properties]] | The scope properties. |
| [[Imports]] | The import list. |
| [[AnnotationImports]] | The annotation import list (used for verifying meta-data). |
Import list
The import list may contain package single imports and package wildcard imports.
Scope variations
Class scope
Added internal properties
| Name | Description |
|---|---|
| [[Class]] | Class object. |
Enum scope
Added internal properties
| Name | Description |
|---|---|
| [[Class]] | Class object. |
Interface scope
Added internal properties
| Name | Description |
|---|---|
| [[Interface]] | The interface. |
Package scope
Added internal properties
| Name | Description |
|---|---|
| [[Package]] | Package. |
Activation
Method bodies create an activation as scope.
Added internal properties
| Name | Description |
|---|---|
| [[This]] | The this object. |
| [[Method]] | Method. |
Implicit scope
The topmost scope from which all scopes inherit is implicitly created by the language.
Imports
The topmost scope imports the top-level package by wildcard. It is allowed to shadow names from the top-level package, in which case, the SX alias may be used to access the top-level package.
The topmost scope imports annotations from sx.annotations.*, as if by including a import@ sx.annotations.* pragma.
SX
The top-level package defines an SX property, which is an alias to a package wildcard import of the top-level package.
meta
The top-level package defines a meta namespace, which is the system meta namespace; used a few times such as for the meta::class method. It is not a reserved word in qualified identifiers as users may need their own namespace prefix with that name.
generic
The top-level package defines a generic namespace, which is useful for the generic::clone method that won’t collide with an user clone.
Intl
The top-level package defines an Intl property, which is an alias to a package wildcard import of the sx.intl package.
Temporal
The top-level package defines an Temporal property, which is an alias to a package wildcard import of the sx.temporal package.
Conditional compilation
The __ns__::constant expression may match a configuration constant used for conditional compilation.
__ns__::constant {
//
}
__ns__::constant var x
The following program uses an inline constant.
trace(__ns__::constant)
Implicit constants
__sx__::debugging
The __sx__::debugging constant indicates whether or not the program is targetting a debug build.
__sx__::releasing
The __sx__::releasing constant indicates whether or not the program is targetting a release build.
__sx__::testing
The __sx__::testing constant indicates whether or not the program is compiling for evaluating unit tests.
Unit testing
Basic unit testing may be done by defining functions annotated with the Test meta-data at a package-level.
package zero.nop.tests {
[Test]
public function foo() : void {
assert.equal(2 + 2, 4)
}
}
Or the user may define a class annotated with the TestSuite meta-data at a package-level, consisting of an empty constructor and instance methods annotated with the Test meta-data.
package zero.nop.tests {
[TestSuite]
public class Tests {
[Test]
public function foo() : void {
//
}
}
}
ShockDoc comments
ShockDoc are documentation comments that use the format /** */. Markdown notation is supported in ShockDoc comments.
For each line, the beginning whitespace is stripped, then the * character and a single following white space character are stripped, and the resting characters are the actual line contents.
Line contents may start with a tag (such as @deprecated). Tags may span multiple lines until the next tag appears; tags that do not accept content do not span any more characters.
Code blocks (whose delimiters consist of at least three backticks ```) as expressed in Markdown cause tags to be ignored in the code content, as in:
/**
* ```plain
* @deprecated
* ```
*/
Local images
ShockDoc comments may refer to relative images through the Markdown notation .
Supported tags
@copy
Copies ShockDoc comment from another definition. Use a #x component to refer to an instance property.
@copy A
@copy A.w
@copy A#x
@copy #x
@default
Default value as a plain expression.
@default exp
@deprecated
@deprecated [Description]
@example
@example
The following...
@event
Indicates that a record type’s property is an event handler. The tag does nothing but move the item to the Events section of the documentation, similiar to how happens with the Event meta-data used in class definitions.
@event
@inheritDoc
Inherits documentation from base class or base class’s item.
@inheritDoc
@internal
Internal comment for an item (not included in the generated documentation).
@internal Comment.
@param
@param paramName Description
@private
Hides an item from the generated documentation.
@private
@return
@return Description
@see
Where item maybe an item reference with optional #x instance property, or just an instance property #x.
@see item [Display text]
@throws
@throws ClassName [Description]
Omitting a package from the API
To omit a package from the API reference, insert a <!-- @private --> comment at the top of its README or README.md file.
Meta-methods
Certain methods of the meta namespace may be implemented in a class or interface for overriding language behavior; those are referred to as meta-methods.
meta::invoke()
A class self-attached meta::invoke() method may be defined with any number of parameters and any result type, overriding the behavior of calling the class object.
meta function invoke():t {}
A multi-method may be used, allowing for multiple call signatures.
meta::get()
Note: Overriding the property accessor with a possibly
stringorQNamekey type (including base types*andObject) will override all names (like.x), except when calling a method (like.m()). In that case, a class is said to be dynamic.
meta function get(key:K):V {
//
}
meta::set()
Note: Overriding the property accessor with a possibly
stringorQNamekey type (including base types*andObject) will override all names (like.x), except when calling a method (like.m()). In that case, a class is said to be dynamic.
meta function set(key:K, value:V):void {
//
}
meta::delete()
Note: Overriding the property accessor with a possibly
stringorQNamekey type (including base types*andObject) will override all names (like.x), except when calling a method (like.m()). In that case, a class is said to be dynamic.
meta function delete(key:K):boolean {
//
}
meta::has()
Overrides the behavior of the in operator.
meta function has(key:K):boolean {
//
}
meta::getAttribute()
Overrides the behavior of the .@k accessor.
meta function getAttribute(key:K):V {
//
}
meta::setAttribute()
Overrides the behavior of the .@k = v accessor.
meta function setAttribute(key:K, value:V):void {
//
}
meta::deleteAttribute()
Overrides the behavior of the delete (...).@k accessor.
meta function deleteAttribute(key:K):boolean {
//
}
meta::filter()
Overrides the behavior of the filter operator (.(test)).
meta function filter(testFn:function(T):boolean):E {
//
}
meta::descendants()
Overrides the behavior of the descendants operator (..x). The parameter is expected to be typed string or QName.
meta function descendants(name:QName):E {
//
}
Lexical conventions
This section defines the lexical grammar of the ShockScript language.
The tokenizer scans one of the following input goal symbols depending on the syntactic context: InputElementDiv, InputElementRegExp, InputElementXMLTag, InputElementPI, InputElementXMLContent.
The following program illustrates how the tokenizer decides which is the input goal symbol to scan:
/(?:)/ ;
a / b ;
<a>Text</a> ;
The following table indicates which is the input goal symbol that is scanned for each of the tokens comprising the previous program:
| Token | Input goal |
|---|---|
| /(?:)/ | InputElementRegExp |
| ; | InputElementDiv |
| a | InputElementRegExp |
| / | InputElementDiv |
| b | InputElementRegExp |
| ; | InputElementDiv |
| < | InputElementRegExp |
| a | InputElementXMLTag |
| > | InputElementXMLTag |
| Text | InputElementXMLContent |
| </ | InputElementXMLContent |
| a | InputElementXMLTag |
| > | InputElementXMLTag |
| ; | InputElementDiv |
The InputElementPI goal symbol must be used while parsing a <?fixed={x}?> expression.
Note: InputElementPI has nothing to do with E4X. It’s currently used in the fixed expression for escaping out of dynamic properties.
Syntax
-
InputElementDiv ::
-
WhiteSpace
LineTerminator
Comment
Identifier
ReservedWord
Punctuator
/
/=
NumericLiteral
StringLiteral
-
InputElementRegExp ::
-
WhiteSpace
LineTerminator
Comment
Identifier
ReservedWord
Punctuator
NumericLiteral
StringLiteral
RegularExpressionLiteral
XMLMarkup
<?fixed={
-
InputElementXMLTag ::
-
XMLName
XMLTagPunctuator
XMLAttributeValue
XMLWhitespace
{
-
InputElementPI ::
-
?>
-
InputElementXMLContent ::
-
XMLMarkup
XMLText
{
< [lookahead ∉ { ?, !, / }]
</
Source Characters
Syntax
-
SourceCharacter ::
-
Unicode code point
-
SourceCharacters ::
-
SourceCharacter SourceCharactersopt
White Space
The WhiteSpace token is filtered out by the lexical scanner.
Syntax
-
WhiteSpace ::
-
U+09 tab
U+0B vertical tab
U+0C form feed
U+20 space
U+A0 no-break space
Unicode “space separator”
Line Terminator
The LineTerminator token is filtered out by the lexical scanner, however it may result in a VirtualSemicolon to be inserted.
Syntax
-
LineTerminator ::
-
U+0A line feed
U+0D carriage return
U+2028 line separator
U+2029 paragraph separator
Comment
The Comment token is filtered out by the lexical scanner, however it propagates any LineTerminator token from its characters.
/*
* /*
* *
* */
*/
Syntax
-
Comment ::
-
// SingleLineCommentCharacters
MultiLineComment
-
SingleLineCommentCharacters ::
-
SingleLineCommentCharacter SingleLineCommentCharactersopt
-
SingleLineCommentCharacter ::
-
[lookahead ∉ { LineTerminator }] SourceCharacter
-
MultiLineComment ::
-
/* MultiLineCommentCharactersopt */
-
MultiLineCommentCharacters ::
-
SourceCharacters [but no embedded sequence /*]
MultiLineComment
MultiLineCommentCharacters SourceCharacters [but no embedded sequence /*]
MultiLineCommentCharacters MultiLineComment
Virtual Semicolon
The VirtualSemicolon nonterminal matches an automatically inserted semicolon, known as a virtual semicolon.
Virtual semicolons are inserted in the following occasions:
- After a right-curly character }
- Before a LineTerminator
Identifier
The Identifier symbol is similiar to that from the ECMA-262 third edition, but with support for scalar Unicode escapes, \xXX escapes and a \x{...} escape (alias to \u{...}).
Syntax
-
Identifier ::
-
IdentifierName [but not ReservedWord or ContextKeyword]
ContextKeyword
-
IdentifierName ::
-
IdentifierStart
IdentifierName IdentifierPart
-
IdentifierStart ::
-
UnicodeLetter
underscore _
$
UnicodeEscapeSequence
-
IdentifierPart ::
-
UnicodeLetter
UnicodeCombiningMark
UnicodeConnectorPunctuation
UnicodeDigit
underscore _
$
UnicodeEscapeSequence
-
UnicodeLetter ::
-
Unicode letter (“L”)
Unicode letter number (“Nl”)
-
UnicodeDigit ::
-
Unicode decimal digit number (“Nd”)
-
UnicodeCombiningMark ::
-
Unicode nonspacing mark (“Mn”)
Unicode spacing combining mark (“Mc”)
-
UnicodeConnectorPunctuation ::
-
Unicode connector punctuation (“Pc”)
Keywords
ReservedWord includes the following reserved words:
as
do
if
in
is
for
let
new
not
try
use
var
case
else
null
this
true
void
with
await
break
catch
class
const
false
super
throw
while
yield
delete
import
public
return
switch
typeof
default
extends
finally
package
private
continue
function
internal
interface
protected
implements
ContextKeyword is one of the following in certain syntactic contexts:
get
map
set
tap
xml
each
enum
meta
type
Embed
final
native
static
decimal
generic
abstract
override
namespace
undefined
Punctuator
Punctuator includes one of the following:
:: @
. .. ...
( ) [ ] { }
: ; ,
? ! =
?.
< <=
> >=
== ===
!= !==
+ - * % **
++ --
<< >> >>>
& ^ | ~
&& ^^ || ??
The @ punctuator must not be followed by a single quote ’ or a double quote character “.
Punctuator includes CompoundAssignmentPunctuator. CompoundAssignmentPunctuator is one of the following:
+= -= *= %= **=
<<= >>= >>>= &= ^= |=
&&= ^^= ||=
??=
Numeric Literal
NumericLiteral is similiar to NumericLiteral from the ECMA-262 third edition, with support for binary literals, underscore separators and certain suffixes:
0b1011
0o77777
0x0A
1_000
10d // double(10) or simply 10
10f // float(10)
10i // int(10)
10m // decimal(10). "m" for money
10n // bigint(10)
10u // uint(10)
Syntax
-
NumericLiteral ::
-
DecimalLiteral DecimalLiteralSuffixopt [lookahead ∉ { IdentifierStart, DecimalDigit }]
HexIntegerLiteral HexLiteralSuffixopt [lookahead ∉ { IdentifierStart, DecimalDigit }]
BinIntegerLiteral BinLiteralSuffixopt [lookahead ∉ { IdentifierStart, DecimalDigit }]
OctalIntegerLiteral OctalLiteralSuffixopt [lookahead ∉ { IdentifierStart, DecimalDigit }]
-
DecimalLiteralSuffix ::
-
d
D
f
F
i
I
m
M
n
N
u
U
-
HexLiteralSuffix ::
-
i
I
m
M
n
N
u
U
-
BinLiteralSuffix ::
-
DecimalLiteralSuffix
-
OctalLiteralSuffix ::
-
DecimalLiteralSuffix
-
DecimalLiteral ::
-
DecimalIntegerLiteral . UnderscoreDecimalDigitsopt
ExponentPartopt
. UnderscoreDecimalDigits ExponentPartopt
DecimalIntegerLiteral ExponentPartopt
-
DecimalIntegerLiteral ::
-
0
[lookahead = NonZeroDigit] UnderscoreDecimalDigitsopt
-
DecimalDigits ::
-
DecimalDigit{1,}
-
UnderscoreDecimalDigits ::
-
DecimalDigits
UnderscoreDecimalDigits _ DecimalDigits
-
DecimalDigit ::
-
0-9
-
NonZeroDigit ::
-
1-9
-
ExponentPart ::
-
ExponentIndicator SignedInteger
-
ExponentIndicator ::
-
e
E
-
SignedInteger ::
-
UnderscoreDecimalDigits
+ UnderscoreDecimalDigits
- UnderscoreDecimalDigits
-
HexIntegerLiteral ::
-
0x UnderscoreHexDigits
0X UnderscoreHexDigits
-
HexDigit ::
-
0-9
A-F
a-f
-
UnderscoreHexDigits ::
-
HexDigit{1,}
UnderscoreHexDigits _ HexDigit{1,}
-
BinIntegerLiteral ::
-
0b UnderscoreBinDigits
0B UnderscoreBinDigits
-
BinDigit ::
-
0
1
-
UnderscoreBinDigits ::
-
BinDigit{1,}
UnderscoreBinDigits _ BinDigit{1,}
-
OctalIntegerLiteral ::
-
0o UnderscoreOctalDigits
0O UnderscoreOctalDigits
-
OctalDigit ::
-
0-7
-
UnderscoreOctalDigits ::
-
OctalDigit{1,}
UnderscoreOctalDigits _ OctalDigit{1,}
Regular Expression Literal
RegularExpressionLiteral is similiar to RegularExpressionLiteral from the ECMA-262 third edition, with support for line breaks.
Syntax
-
RegularExpressionLiteral ::
-
/ RegularExpressionBody / RegularExpressionFlags
-
RegularExpressionBody ::
-
RegularExpressionFirstChar RegularExpressionChars
-
RegularExpressionChars ::
-
«empty»
RegularExpressionChars RegularExpressionChar
-
RegularExpressionFirstChar ::
-
SourceCharacter [but not * or \ or /]
BackslashSequence
-
RegularExpressionChar ::
-
SourceCharacter [but not \ or /]
BackslashSequence
-
BackslashSequence ::
-
\ SourceCharacter
-
RegularExpressionFlags ::
-
«empty»
RegularExpressionFlags IdentifierPart
String Literal
StringLiteral is similiar to the StringLiteral symbol from the ECMA-262 third edition. The following additional features are included:
- Scalar UnicodeEscapeSequence using the
\u{...}or\x{...}form - Triple strings
- Raw strings using the
@prefix
Triple string literals use either """ or ''' as delimiter and may span multiple lines. The contents of triple string literals are indentation-based, as can be observed in the following program:
const text = """
foo
bar
"""
text == "foo\nbar"
Triple strings are processed as follows:
- The base line for determining nested indentation characters is the non-empty (i.e. not a whitespace only line) first line that contains the lowest-indentation level after whitespace characters.
- Every line contents start from the base line’s first non-whitespace character.
- Beginning and end lines that are empty or consist only of whitespace are discarded.
Both regular and triple strings accept the @ prefix, designating raw string literals. Raw string literals contain no escape sequences.
const text = @"""
x\y
"""
Escape sequences are described by the following table:
| Escape | Description |
|---|---|
| \’ | U+27 single-quote |
| \“ | U+22 double-quote |
| \\ | U+5C backslash character |
| \b | U+08 backspace character |
| \f | U+0C form feed character |
| \n | U+0A line feed character |
| \r | U+0D carriage return character |
| \t | U+09 tab character |
| \v | U+0B vertical tab character |
| \0 | U+00 character |
| \xHH | Contributes an Unicode code point value |
| \uHHHH | Contributes an Unicode code point value |
| \u{…} | Contributes an Unicode code point value |
| \ followed by LineTerminator | Contributes nothing |
Syntax
-
StringLiteral ::
-
[lookahead ≠ """] " DoubleStringCharacter{0,} "
[lookahead ≠ '''] ' SingleStringCharacter{0,} '
""" TripleDoubleStringCharacter{0,} """
''' TripleSingleStringCharacter{0,} '''
RawStringLiteral
-
RawStringLiteral ::
-
@ [lookahead ≠ """] " DoubleStringRawCharacter{0,} "
@ [lookahead ≠ '''] ' SingleStringRawCharacter{0,} '
@""" TripleDoubleStringRawCharacter{0,} """
@''' TripleSingleStringRawCharacter{0,} '''
-
DoubleStringCharacter ::
-
SourceCharacter [but not double-quote " or backslash \ or LineTerminator]
EscapeSequence
-
SingleStringCharacter ::
-
SourceCharacter [but not single-quote ' or backslash \ or LineTerminator]
EscapeSequence
-
DoubleStringRawCharacter ::
-
SourceCharacter [but not double-quote " or LineTerminator]
-
SingleStringRawCharacter ::
-
SourceCharacter [but not single-quote ' or LineTerminator]
-
TripleDoubleStringCharacter ::
-
[lookahead ≠ """] SourceCharacter [but not backslash \ or LineTerminator]
EscapeSequence
LineTerminator
-
TripleSingleStringCharacter ::
-
[lookahead ≠ '''] SourceCharacter [but not backslash \ or LineTerminator]
EscapeSequence
LineTerminator
-
TripleDoubleStringRawCharacter ::
-
[lookahead ≠ """] SourceCharacter [but not LineTerminator]
LineTerminator
-
TripleSingleStringRawCharacter ::
-
[lookahead ≠ '''] SourceCharacter [but not LineTerminator]
LineTerminator
Escape Sequences
Syntax
-
EscapeSequence ::
-
\ CharacterEscapeSequence
\0 [lookahead ∉ DecimalDigit]
\ LineTerminator
UnicodeEscapeSequence
-
CharacterEscapeSequence ::
-
SingleEscapeCharacter
NonEscapeCharacter
-
SingleEscapeCharacter ::
-
'
"
\
b
f
n
r
t
v
-
NonEscapeCharacter ::
-
SourceCharacter [but not EscapeCharacter or LineTerminator]
-
EscapeCharacter ::
-
SingleEscapeCharacter
DecimalDigit
x
u
-
UnicodeEscapeSequence ::
-
\x HexDigit HexDigit
\u HexDigit{4}
\x { HexDigit{1,} }
\u { HexDigit{1,} }
XML
This section defines nonterminals used in the lexical grammar as part of the XML capabilities of the ShockScript language.
If a XMLMarkup, XMLAttributeValue or XMLText contains a LineTerminator after parsed, it contributes such LineTerminator to the lexical scanner.
Syntax
-
XMLMarkup ::
-
XMLComment
XMLCDATA
XMLPI
-
XMLWhitespaceCharacter ::
-
U+20 space
U+09 tab
U+0D carriage return
U+0A line feed
-
XMLWhitespace ::
-
XMLWhitespaceCharacter
XMLWhitespace XMLWhitespaceCharacter
-
XMLText ::
-
SourceCharacters [but no embedded left-curly { or less-than <]
-
XMLName ::
-
XMLNameStart
XMLName XMLNamePart
-
XMLNameStart ::
-
UnicodeLetter
underscore _
colon :
-
XMLNamePart ::
-
UnicodeLetter
UnicodeDigit
period .
hyphen -
underscore _
colon :
-
XMLComment ::
-
<!-- XMLCommentCharactersopt -->
-
XMLCommentCharacters ::
-
SourceCharacters [but no embedded sequence -->]
-
XMLCDATA ::
-
<![CDATA[ XMLCDATACharacters ]]>
-
XMLCDATACharacters ::
-
SourceCharacters [but no embedded sequence ]]>]
-
XMLPI ::
-
<? XMLPICharactersopt ?>
-
XMLPICharacters ::
-
SourceCharacters [but no embedded sequence ?>]
-
XMLAttributeValue ::
-
" XMLDoubleStringCharactersopt "
' XMLSingleStringCharactersopt '
-
XMLDoubleStringCharacters ::
-
SourceCharacters [but no embedded double-quote "]
-
XMLSingleStringCharacters ::
-
SourceCharacters [but no embedded single-quote ']
-
XMLTagPunctuator ::
-
=
&=
>
/>
Semantics
XMLCDATA contents, excluding the <![CDATA[ opening sequence and the ]]> closing sequence, are processed the same way as triple strings:
- The base line for determining nested indentation characters is the non-empty (i.e. not a whitespace only line) first line that contains the lowest-indentation level after whitespace characters.
- Every line contents start from the base line’s first non-whitespace character.
- Beginning and end lines that are empty or consist only of whitespace are discarded.
For XMLText, unlike the E4X standard, ShockScript always trims any whitespace at the beginning and end of the text. The parser can skip the token if its empty after trimming whitespace. Note that this does not apply to the XML or XMLList parsers during runtime; they ignore whitespace depending on the XMLContext object specified by the use xml pragma.
ShockScript: Expressions
The syntactic grammar for expressions declares the β superscript, which denotes a pair of definitions: allowIn and noIn.
Identifiers
Syntax
x
*
q::x
q::[k] ;
(q)::x ;
(q)::[k] ;
@x
@[k]
@q::x
@q::[k]
@(q)::x
@(q)::[k]
-
PropertyIdentifier :
-
Identifier [when keywords are enabled]
IdentifierName [when keywords are disabled]
*
-
Qualifier :
-
PropertyIdentifier
ReservedNamespace
-
ReservedNamespace :
-
public
private
protected
internal
-
SimpleQualifiedIdentifier :
-
PropertyIdentifier
Qualifier :: PropertyIdentifier
Qualifier :: Brackets
-
ExpressionQualifiedIdentifier :
-
ParenExpression :: PropertyIdentifier
ParenExpression :: Brackets
-
NonAttributeQualifiedIdentifier :
-
SimpleQualifiedIdentifier
ExpressionQualifiedIdentifier
-
QualifiedIdentifier :
-
@ Brackets
@ NonAttributeQualifiedIdentifier
NonAttributeQualifiedIdentifier
Primary expressions
Syntax
-
PrimaryExpression :
-
NullLiteral
BooleanLiteral
NumericLiteral
StringLiteral
ThisLiteral
AllLiteral
RegularExpressionLiteral
QualifiedIdentifier
XMLLiteral
FixedExpression
ParenListExpression
ArrayLiteral
ObjectLiteral
EmbedExpression
Null literal
Syntax
-
NullLiteral :
-
null
This literal
Syntax
-
ThisLiteral :
-
this
All literal
Syntax
**
-
AllLiteral :
-
**
Semantics
The all literal returns a value of a Flags enumeration filled with the all flags from that Flags enumeration. The context type must be a Flags enumeration.
Boolean literal
Syntax
-
BooleanLiteral :
-
true
false
Numeric literal
Syntax
10
10.0
.0
10e5
1e+9
1e-9
0b1011
0xFF
10_000
String literal
Syntax
"shockscript"
Triple string literals span multiple lines and are indentation-aware:
"""
shockscript, nicely beauty
scripting.
""" == "shockscript, nicely beauty\nscripting."
Semantics
A string literal may be assigned to a simple enumeration at compile-time, matching a variant’s string; therefore returning a value whose static type is that enumeration.
A string literal may be assigned to int, uint, byte (ASCII) or the Number union, resulting into the integer value identifying the single Unicode Code Point in the string literal. In that case, it is a verify error if the string literal does not contain exactly one Code Point (which may be an escape sequence itself).
"A".charCodeAt(0) == "A"
Regular expression literal
Syntax
/(?:)/gi
XML literal
Syntax
-
XMLLiteral :
-
XMLMarkup
XMLElement
< > XMLElementContent </ >
-
XMLElement :
-
< XMLTagContent XMLWhitespaceopt />
< XMLTagContent XMLWhitespaceopt > XMLElementContent </ XMLTagName XMLWhitespaceopt >
-
XMLTagContent :
-
XMLTagName XMLAttributes
-
XMLTagName :
-
{ AssignmentExpressionallowIn }
XMLName
-
XMLAttributes :
-
XMLWhitespace { AssignmentExpressionallowIn }
XMLAttribute XMLAttributes
«empty»
-
XMLAttribute :
-
XMLWhitespace XMLName [lookahead ≠ XMLWhitespaceopt = ] [lookahead ≠ XMLWhitespaceopt &= ]
XMLWhitespace XMLName XMLWhitespaceopt = XMLWhitespaceopt { AssignmentExpressionallowIn }
XMLWhitespace XMLName XMLWhitespaceopt &= XMLWhitespaceopt Block
XMLWhitespace XMLName XMLWhitespaceopt = XMLWhitespaceopt XMLAttributeValue
-
XMLElementContent :
-
{ AssignmentExpressionallowIn } XMLElementContent
XMLMarkup XMLElementContent
XMLText XMLElementContent
XMLElement XMLElementContent
«empty»
Semantics
Unlike the E4X standard, ShockScript always trims any whitespace at the beginning and end of text nodes within XML literals regardless of the XMLContext object.
CDATA is indentation-aware similar to triple strings.
Note: If whitespace is desired on text nodes, one may use a CDATA section.
One reason for always trimming whitespace is that the source text may be autoformatted without breaking certain rules; although, CDATA sections may be trickier to format due to indentation, thus requiring type checking for determining whether the compiler understands such a section as an actual computer language such as a style block.
For general CDATA, an autoformatter will retain indentation starting from the lowest indent level.
Array literal
Syntax
["shock", "script"]
The following:
[0, 1, 2] : [byte]
is equivalent to Array.<byte>([0, 1, 2]), or:
type B = [byte]
B([0, 1, 2])
-
ArrayLiteral :
-
[ Elisionopt ] ArrayLiteralTypeAnnotationopt
[ ElementList ] ArrayLiteralTypeAnnotationopt
[ ElementList , Elisionopt ] ArrayLiteralTypeAnnotationopt
-
ArrayLiteralTypeAnnotation :
-
: TypeExpression
-
Elision :
-
,
Elision ,
-
ElementList :
-
Elisionopt AssignmentExpressionallowIn
Elisionopt LiteralRest
ElementList , Elisionopt AssignmentExpressionallowIn
ElementList , Elisionopt LiteralRest
Object literal
Syntax
-
ObjectLiteral :
-
{ FieldList }
-
FieldList :
-
«empty»
NonEmptyFieldList
NonEmptyFieldList ,
-
NonEmptyFieldList :
-
LiteralField
LiteralRest
NonEmptyFieldList , LiteralField
NonEmptyFieldList , LiteralRest
-
LiteralRest :
-
... AssignmentExpressionallowIn
-
LiteralField :
-
FieldName : AssignmentExpressionallowIn
NonAttributeQualifiedIdentifier
-
FieldName :
-
NonAttributeQualifiedIdentifier
Brackets
StringLiteral
NumericLiteral
Embed expression
Syntax
Embed("flower.webp")
// force embedding externally
// even if file is short
Embed("flower.webp", external="true")
// UTF-8 text
Embed("data.txt", type="text/plain")
// ByteArray
Embed("data.bin", type="application/octet-stream")
-
EmbedExpression :
-
Embed ( MetadataEntryListopt )
Semantics
The default form of the embed expression specifying solely a path is implementation-defined, but always returns a string representing an URL or resource path.
- External resources are typically embedded in a structured way in the final program, using project ID + source path + resource path + filename, which is also useful for embedding licensed resources such as fonts.
The form that specifies type="text/plain" will embed the referenced file at the program’s static memory as an UTF-8 encoded text, returning the string data type.
The form that specifies type="application/octet-stream" will embed the referenced file at the program’s static memory as an octet stream, returning the ByteArray data type.
Fixed expression
Syntax
<?fixed={x}?>
-
FixedExpression :
-
<?fixed={ ListExpressionallowIn } ?>
Semantics
Deactivates lookup of dynamic properties (implemented through a meta-method such as meta::get(k) where k includes string or QName) in the enclosed expression.
Note: This is necessary in rare cases where, for example, a dynamic class needs to access internal data. In such cases, instance data are not accessible unless the user uses the fixed expression
<?fixed={x}?>. In the case of lexical name resolution with athisreceiver, the fixed expression is not necessary as in-scope lookup skips dynamic names.
The fixed name lookup effect is propagated:
- From parenthesized expressions to the inner expression
- From dot operator to base expression
- From brackets operator to base expression
Parenthesized expressions
Syntax
(x)
-
ParenExpression :
-
( AssignmentExpressionallowIn )
-
ParenListExpression :
-
ParenExpression
( ListExpressionallowIn , AssignmentExpressionallowIn )
Super expression
Syntax
-
SuperExpression :
-
super
super Arguments
Postfix expressions
Syntax
-
PostfixExpression :
-
FullPostfixExpression
ShortNewExpression
-
FullPostfixExpression :
-
PrimaryExpression
FullNewExpression
FullPostfixExpression PropertyOperator
SuperExpression PropertyOperator
FullPostfixExpression NonNull
FullPostfixExpression Arguments
FullPostfixExpression TypeArguments
FullPostfixExpression QueryOperator
FullPostfixExpression [no line break] ++
FullPostfixExpression [no line break] --
FullPostfixExpression OptionalChaining
-
NonNull :
-
!
-
OptionalChaining :
-
?. QualifiedIdentifier
?. Brackets
?. Arguments
OptionalChaining PropertyOperator
OptionalChaining NonNull
OptionalChaining Arguments
OptionalChaining TypeArguments
OptionalChaining QueryOperator
OptionalChaining OptionalChaining
Property accessors
Syntax
-
PropertyOperator :
-
. QualifiedIdentifier
Brackets
-
Brackets :
-
[ ListExpressionallowIn ]
Query operators
Syntax
-
QueryOperator :
-
.. QualifiedIdentifier
. ( ListExpressionallowIn )
Descendants operator
o..x
Semantics
The descendants operator o..x looks at o for the meta::descendants() method and returns the result of invoking that method with the given identifier key.
Filter operator
o.(*.name.startsWith("A"))
Semantics
The filter operator o.(...) looks at o for the meta::filter() method and creates a wildcard * binding inside the parenthesized expression that represents the element being tested. The parenthesized expression must return a boolean and represents an activation.
Call expressions
Syntax
-
Arguments :
-
( )
( ListExpressionallowIn )
( ListExpressionallowIn , )
-
ArgumentListallowIn :
-
AssignmentExpressionβ
ArgumentListβ , AssignmentExpressionβ
New expressions
Syntax
-
FullNewExpression :
-
new FullNewSubexpression Arguments
-
FullNewSubexpression :
-
PrimaryExpression
FullNewExpression
FullNewSubexpression PropertyOperator
SuperExpression PropertyOperator
-
ShortNewExpression :
-
new ShortNewSubexpression
-
ShortNewSubexpression :
-
FullNewSubexpression
ShortNewExpression
Semantics
The new expression is specialized for the Array and Map types. If the class of new expression is untyped and it’s an Array or Map, it uses * for all expected type parameters.
Unary expressions
Syntax
-
UnaryExpression :
-
PostfixExpression
delete PostfixExpression
void UnaryExpression
await UnaryExpression
typeof UnaryExpression
++ PostfixExpression
-- PostfixExpression
+ UnaryExpression
- UnaryExpression
~ UnaryExpression
! UnaryExpression
Binary expressions
The binary operators are left-associative, excluding the following cases:
- The exponentiation operator ** is right-associative.
The short circuit operators (||, ^^) have the lowest precedence and the exponentiation operator (**) has the greatest precedence.
Exponentiation expressions
Syntax
-
ExponentiationExpression :
-
UnaryExpression
UnaryExpression ** ExponentiationExpression
Multiplicative expressions
Syntax
-
MultiplicativeExpression :
-
ExponentiationExpression
MultiplicativeExpression * ExponentiationExpression
MultiplicativeExpression / ExponentiationExpression
MultiplicativeExpression % ExponentiationExpression
Additive expressions
Syntax
-
AdditiveExpression :
-
MultiplicativeExpression
AdditiveExpression + MultiplicativeExpression
AdditiveExpression - MultiplicativeExpression
Shift expressions
Syntax
-
ShiftExpression :
-
AdditiveExpression
ShiftExpression << AdditiveExpression
ShiftExpression >> AdditiveExpression
ShiftExpression >>> AdditiveExpression
Relational expressions
Syntax
-
RelationalExpressionβ :
-
ShiftExpression
RelationalExpressionβ > ShiftExpression
RelationalExpressionβ < ShiftExpression
RelationalExpressionβ <= ShiftExpression
RelationalExpressionβ >= ShiftExpression
RelationalExpressionβ as [lookahead ≠ ! ] ShiftExpression
RelationalExpressionβ as ! ShiftExpression
RelationalExpressionβ is ShiftExpression
RelationalExpressionβ is not ShiftExpression
[β = allowIn] RelationalExpressionβ in ShiftExpression
[β = allowIn] RelationalExpressionβ not in ShiftExpression
Equality expressions
Syntax
-
EqualityExpressionβ :
-
RelationalExpressionβ
EqualityExpressionβ == RelationalExpressionβ
EqualityExpressionβ != RelationalExpressionβ
EqualityExpressionβ === RelationalExpressionβ
EqualityExpressionβ !== RelationalExpressionβ
Bitwise expressions
Syntax
-
BitwiseAndExpressionβ :
-
EqualityExpressionβ
BitwiseAndExpressionβ & EqualityExpressionβ
-
BitwiseXorExpressionallowIn :
-
BitwiseAndExpressionβ
BitwiseXorExpressionβ ^ BitwiseAndExpressionβ
-
BitwiseOrExpressionallowIn :
-
BitwiseXorExpressionβ
BitwiseOrExpressionβ | BitwiseXorExpressionβ
Logical expressions
Syntax
-
LogicalAndExpressionβ :
-
BitwiseOrExpressionβ
LogicalAndExpressionβ && BitwiseOrExpressionβ
-
LogicalXorExpressionallowIn :
-
LogicalAndExpressionβ
LogicalXorExpressionβ ^^ LogicalAndExpressionβ
-
LogicalOrExpressionallowIn :
-
LogicalXorExpressionβ
LogicalOrExpressionβ || LogicalXorExpressionβ
Coalesce expression
Syntax
-
CoalesceExpressionβ :
-
CoalesceExpressionHeadβ ?? BitwiseOrExpressionβ
-
CoalesceExpressionHeadβ :
-
CoalesceExpressionβ
BitwiseOrExpressionβ
Short circuit expressions
Syntax
-
ShortCircuitExpressionβ :
-
LogicalOrExpressionβ
CoalesceExpressionβ
Conditional expressions
Syntax
-
ConditionalExpressionβ :
-
ShortCircuitExpressionβ
ShortCircuitExpressionβ ? AssignmentExpressionβ : AssignmentExpressionβ
Non assignment expressions
Syntax
-
NonAssignmentExpressionβ :
-
ShortCircuitExpressionβ
yield [no line break] NonAssignmentExpressionβ
FunctionExpressionβ
ShortCircuitExpressionβ ? NonAssignmentExpressionβ : NonAssignmentExpressionβ
Assignment expressions
Syntax
-
AssignmentExpressionβ :
-
ConditionalExpressionβ
yield [no line break] AssignmentExpressionβ
FunctionExpressionβ
AssignmentLeftHandSide = AssignmentExpressionβ
PostfixExpression CompoundAssignmentPunctuator AssignmentExpressionβ
PostfixExpression /= AssignmentExpressionβ
-
AssignmentLeftHandSide :
-
ArrayPattern
ObjectPattern
PostfixExpression [but not ArrayLiteral or ObjectLiteral]
Function expression
Syntax
-
FunctionExpressionβ :
-
function FunctionCommonβ
function IdentifierName FunctionCommonβ
List expressions
Syntax
-
ListExpressionβ :
-
AssignmentExpressionβ
ListExpressionβ , AssignmentExpressionβ
ShockScript: Type expressions
Syntax
-
TypeExpression :
-
*
void
null
[lookahead ≠ ( ] [lookahead ≠ map { ] [lookahead ≠ tap { ] QualifiedIdentifier
( TypeExpression )
( TypeExpression , TypeExpressionList )
( TypeExpression , TypeExpressionList , )
TupleTypeExpression
map RecordTypeExpression
tap RecordTypeExpression
FunctionTypeExpression
TypeExpression PropertyOperator
TypeExpression [lookahead = . ] QueryOperator
TypeExpression TypeArguments
TypeExpression NonNull
TypeExpression ?
-
TypeExpressionList :
-
TypeExpression
TypeExpressionList , TypeExpression
-
TupleTypeExpression :
-
[ TypeExpression ]
[ TypeExpression , TypeExpressionList ]
[ TypeExpression , TypeExpressionList , ]
-
RecordTypeExpression :
-
{}
{ RecordTypeItemList }
{ RecordTypeItemList }
-
RecordTypeItemList :
-
RecordTypeField
RecordTypeField , RecordTypeItemList
... TypeExpression
-
RecordTypeField :
-
NonAttributeQualifiedIdentifier : TypeExpression
-
FunctionTypeExpression :
-
function ( ) : TypeExpression
function ( FunctionTypeParameterList ) : TypeExpression
function ( FunctionTypeParameterList , ) : TypeExpression
-
FunctionTypeParameterList :
-
FunctionTypeParameter
FunctionTypeParameterList , FunctionTypeParameter
-
FunctionTypeParameter :
-
... TypeExpressionopt
TypeExpression [lookahead ≠ = ]
TypeExpression =
-
TypeArguments :
-
. < TypeArgumentsList ParameterizedGreaterThan
-
ParameterizedGreaterThan :
-
>
first greater-than > from the offending token
-
TypeArgumentsList :
-
TypeExpression
TypeArgumentsList , TypeExpression
ShockScript: Patterns
Matching patterns may be used in a number of contexts, including variable bindings, try..catch clauses, switch type cases, if..let and assignment left-hand sides.
Where applicable, expressions are disambiguated into those patterns, in which case any incompatible or illegal expression results in a syntax error; for example, an expression is disambiguated into a pattern inside an assignment whose left-hand side is either an array or object literal.
Syntax
-
Pattern :
-
[lookahead ≠ undefined if pattern is nested] IdentifierPattern
ArrayPattern
ObjectPattern
ConstantPattern [if pattern is nested]
Pattern NonNull
-
TypedPattern :
-
Pattern [lookahead ≠ :]
Pattern : TypeExpression
Identifier pattern
An identifier pattern is most commonly used for variable bindings; however it can also be used for pattern matching within algebraic data types by using call-like parentheses, where it can also match parameters or fields using compile-time constants.
x
Nothing()
Vector(x, y)
Vector(*, y) // skips "x"
Vector({ x, y })
Syntax
-
IdentifierPattern :
-
IdentifierPatternStart IdentifierPatternContinue{0,} IdentifierPatternArgumentsopt
-
IdentifierPatternStart :
-
Identifier [when keywords are enabled]
IdentifierName [when keywords are disabled]
-
IdentifierPatternContinue :
-
. IdentifierName
-
IdentifierPatternArguments :
-
( )
( IdentifierPatternArgument )
( IdentifierPatternArgument , )
-
IdentifierPatternArgument :
-
*
Pattern
IdentifierPatternArgument , Pattern
IdentifierPatternArgument , *
Array pattern
Syntax
-
ArrayPattern :
-
[]
[ ArrayPatternItemList ]
-
ArrayPatternItemList :
-
,
, ArrayPatternItemList
Pattern
Pattern , ArrayPatternItemList
... Pattern
Semantics
Applications:
- May be used to destructure an
Array-like collection. - May be used to destructure a tuple.
- May be used to destructure an algebraic enumeration’s variant.
Object pattern
Syntax
-
ObjectPattern :
-
{}
{ ObjectPatternFieldList }
-
ObjectPatternFieldList :
-
ObjectPatternField
ObjectPatternField ,
ObjectPatternField , ObjectPatternFieldList
-
ObjectPatternField :
-
FieldName : Pattern
NonAttributeQualifiedIdentifier
Constant pattern
Syntax
-
ConstantPattern :
-
NumericLiteral
StringLiteral
NullLiteral
BooleanLiteral
undefined
ShockScript: Statements
The ω superscript used throughout the specification translates to one of { abbrev, noShortIf, full }.
Syntax
-
Statementω :
-
SuperStatement Semicolonω
Block
IfStatementω
IfLetStatementω
SwitchStatement
DoStatement Semicolonω
WhileStatementω
ForStatementω
WithStatementω
ContinueStatement Semicolonω
BreakStatement Semicolonω
ReturnStatement Semicolonω
ThrowStatement Semicolonω
TryStatement
ExpressionStatement Semicolonω
DefaultXMLNamespaceStatement Semicolonω
LabeledStatementω
-
Substatementω :
-
EmptyStatement
Statementω
-
Substatements :
-
«empty»
SubstatementsPrefix Substatementabbrev
-
SubstatementsPrefix :
-
«empty»
SubstatementsPrefix Substatementfull
-
Semicolonabbrev :
-
;
VirtualSemicolon
«empty»
-
SemicolonnoShortIf :
-
Semicolonabbrev
-
Semicolonfull :
-
;
VirtualSemicolon
Empty statement
Syntax
-
EmptyStatement :
-
;
Expression statement
Syntax
-
ExpressionStatement :
-
[lookahead ∉ { function, { }] ListExpressionallowIn
Default XML namespace statement
The default xml namespace statement is used to specify the default E4X namespace used for lookups where the XML prefix is omitted, influencing the surrounding frame’s [[DefaultNamespace]] internal property; in other words, it can be said that default xml namespace is block-scoped.
namespace Samurai = "http://www.samurai.com/2007"
{
default xml namespace = Samurai
xn = <Envato>swiss</Envato>
}
This affects not only the lexical scope, but also external function calls that are called subsequently from the same scope or nested scopes during runtime.
Note: Internally functions receive a hierarchical environment frame reference, which is decently optimized, where every frame contains a ?context local that includes the [[DefaultNamespace]] internal property that may be a null pointer (which means skip to parent frame).
Thus, that syntactic construct should work with both synchronous and asynchronous code.
Syntax
-
DefaultXMLNamespaceStatement :
-
default [no line break] xml [no line break] namespace = NonAssignmentExpressionallowIn
Super statement
Syntax
-
SuperStatement :
-
super Arguments
Block statement
Syntax
-
Block :
-
{ Directives }
Labeled statement
Syntax
-
LabeledStatementω :
-
Identifier : Substatementω
If statement
Syntax
-
IfStatementabbrev :
-
if ParenListExpression Substatementabbrev
if ParenListExpression SubstatementnoShortIf else Substatementabbrev
-
IfStatementfull :
-
if ParenListExpression Substatementfull
if ParenListExpression SubstatementnoShortIf else Substatementfull
-
IfStatementnoShortIf :
-
if ParenListExpression SubstatementnoShortIf else SubstatementnoShortIf
If let statement
The if..let statement may be used for pattern matching on algebraic data types.
if (let Plus(10, right) = exp) {
//
}
It may also be used for extracting a non-nullable variable from a test expression, without using a matching pattern; that is, a regular identifier binding.
if (let node = parseAtom()) {
//
}
if..let can also be useful for, say, iterator results.
if (let [x!,false] = characters.next()) {
//
} else {
break;
}
Syntax
-
IfLetStatementabbrev :
-
if ( IfLetVariable ) Substatementabbrev
if ( IfLetVariable ) SubstatementnoShortIf else Substatementabbrev
-
IfLetStatementfull :
-
if ( IfLetVariable ) Substatementfull
if ( IfLetVariable ) SubstatementnoShortIf else Substatementfull
-
IfLetStatementnoShortIf :
-
if ( IfLetVariable ) SubstatementnoShortIf else SubstatementnoShortIf
-
IfLetVariable :
-
VariableDefinitionKind VariableBindingallowIn
Switch statements
The switch statement is similiar to that of Java. Unlike Java, the switch statement does not include fallthroughs.
switch (v) {
case 0:
case 1:
trace("zero or one");
default:
trace("other");
}
The switch type statement is used to match the type of a discriminant value.
switch type (v) {
case (d : Date) {
}
default {
}
}
The switch type statement also supports pattern matching on algebraic data types.
switch type (exp) {
case (Plus(10, right)) {
}
}
Syntax
-
SwitchStatement :
-
switch ParenListExpression { CaseElementsabbrev }
switch [no line break] type ParenListExpression { TypeCaseElements }
-
CaseElementsω :
-
«empty»
CaseElementω
CaseElementsfull CaseElementω
-
CaseElementω :
-
CaseLabel{1,} CaseDirectivesω
-
CaseLabel :
-
case ListExpressionallowIn :
default :
-
CaseDirectivesω :
-
Directiveω
CaseDirectivesfull Directiveω
-
TypeCaseElements :
-
«empty»
TypeCaseElement
TypeCaseElements TypeCaseElement
-
TypeCaseElement :
-
case ( TypedPattern ) Block
default Block
Do statement
Syntax
-
DoStatement :
-
do Substatementabbrev while ParenListExpression
While statement
Syntax
-
WhileStatementω :
-
while ParenListExpression Substatementω
For statements
The for..in statement is used to iterate the keys of an object.
for (const key in map) {
trace(key)
}
The for each statement is used to iterate the values of an object.
for each (const value in array) {
trace(value)
}
Syntax
-
ForStatementω :
-
for ( ForInitializer ; ListExpressionallowInopt ; ListExpressionallowInopt ) Substatementω
for ( ForInBinding in ListExpressionallowIn ) Substatementω
for [no line break] each ( ForInBinding in ListExpressionallowIn ) Substatementω
-
ForInitializer :
-
«empty»
ListExpressionnoIn
VariableDefinitionnoIn
-
ForInBinding :
-
PostfixExpression
VariableDefinitionKind VariableBindingnoIn
Continue statement
Syntax
-
ContinueStatement :
-
continue
continue [no line break] Identifier
Break statement
Syntax
-
BreakStatement :
-
break
break [no line break] Identifier
With statement
The with statement is used to declare a * binding to the statement’s scope. The * binding holds the value of the parenthesized expression.
with (o) {
*.x += 10;
*.y += 5;
}
Syntax
-
WithStatementω :
-
with ParenListExpression Substatementω
Return statement
Syntax
-
ReturnStatement :
-
return
return [no line break] ListExpressionallowIn
Throw statement
Syntax
-
ThrowStatement :
-
throw [no line break] ListExpressionallowIn
Try statement
Syntax
-
TryStatement :
-
try Block CatchClauses
try Block CatchClausesopt finally Block
-
CatchClauses :
-
CatchClause
CatchClauses CatchClause
-
CatchClause :
-
catch ( TypedPattern ) Block
ShockScript: Directives
Syntax
-
Directiveω :
-
EmptyStatement
Statementω
ConfigurationConstantopt Attributesopt AnnotatableDirectiveω
ConfigurationConstant Block
ImportDirective Semicolonω
ImportAnnotationDirective Semicolonω
UseNamespaceDirective Semicolonω
UseDecimalDirective Semicolonω
UseXMLDirective Semicolonω
-
AnnotatableDirectiveω :
-
NamespaceDefinition Semicolonω
VariableDefinitionallowIn Semicolonω
FunctionDefinition
ClassDefinition
EnumDefinition
InterfaceDefinition
TypeDefinitionω
-
Directives :
-
«empty»
DirectivesPrefix Directiveabbrev
-
DirectivesPrefix :
-
«empty»
DirectivesPrefix Directivefull
-
ConfigurationConstant :
-
Identifier :: IdentifierName
Attributes
Attributes are in the sequence of meta-data followed by modifiers. A parser shall disambiguate expressions into attributes as applicable.
Syntax
-
Attributes :
-
Attribute AttributeLineBreakRestriction
AttributeCombination AttributeLineBreakRestriction
-
AttributeCombination :
-
Attribute AttributeLineBreakRestriction Attributes
-
BlockAttributes :
-
Metadata
BlockAttributes Metadata
-
AttributeLineBreakRestriction :
-
no line break if the previous and offending tokens match an IdentifierName
-
Attribute :
-
Metadata
UserAttribute
ReservedNamespace
final
native
static
abstract
override
generic
-
UserAttribute :
-
Identifier
UserAttribute . IdentifierName
-
Metadata :
-
MetadataPreRestriction [ MetadataForm ]]
MetadataPreRestriction [ MetadataForm MetadataTrailingComma ]
-
MetadataPreRestriction :
-
if the Metadata is in the beginning of Attributes or if the Metadata appears before an IdentifierName in Attributes
-
MetadataTrailingComma :
-
comma , if the Metadata is the first occurrence in Attributes or BlockAttributes
-
MetadataForm :
-
MetadataName
MetadataName ()
MetadataName ( MetadataEntryList )
MetadataName ( MetadataEntryList , )
-
MetadataName :
-
Identifier
Identifier :: IdentifierName
-
MetadataEntryList :
-
MetadataEntry
MetadataEntryList , MetadataEntry
-
MetadataEntry :
-
MetadataName
StringLiteral
MetadataName = StringLiteral
Import directive
The import pragma may be used to import properties from a package.
The import@ pragma may be used to import annotations from a package.
import@ bridge.annotations.*;
Syntax
-
ImportDirective :
-
import PackageName . *
import PackageName . IdentifierName
import Identifier = PackageName . *
import Identifier = PackageName . IdentifierName
-
ImportAnnotationDirective :
-
import @ PackageName . *
import @ PackageName . IdentifierName
Use namespace directive
The use namespace pragma is used to contribute a namespace to the compile-time open namespace list of the enclosing scope.
use namespace ns1
use namespace belongs to the compile-time and only affects the open namespace list of the current block scope and any syntactically nested scopes, unlike, say, default xml namespace.
Syntax
-
UseNamespaceDirective :
-
use namespace ListExpressionallowIn
Use decimal directive
The use decimal pragma is used to provide a DecimalContext instance at the surrounding frame for configuring the decimal data type.
var ctx:DecimalContext, x:decimal
ctx = new DecimalContext(12, "half_even") // 12 digits of precision,
// round to even
{
use decimal ctx
x = a + b // "+" rounds to even if necessary
}
This affects not only the lexical scope, but also external function calls that are called subsequently from the same scope or nested scopes during runtime.
Note: Internally functions receive a hierarchical environment frame reference, which is decently optimized, where every frame contains a ?context local that includes a
DecimalContextreference that may be a null pointer (which means skip to parent frame).Thus, that syntactic construct should work with both synchronous and asynchronous code.
Syntax
-
UseDecimalDirective :
-
use decimal ListExpressionallowIn
Use XML directive
The use xml pragma is used to provide a XMLContext object at the surrounding frame for configuring the XML/XMLList data types.
var ctx:XMLContext, xn:XML
ctx = { ignoreWhitespace: true }
{
use xml ctx
xn = <information>{" content "}</information>
}
This affects not only the lexical scope, but also external function calls that are called subsequently from the same scope or nested scopes during runtime.
Note: Internally functions receive a hierarchical environment frame reference, which is decently optimized, where every frame contains a ?context local that includes a
XMLContextreference that may be a null pointer (which means skip to parent frame).Thus, that syntactic construct should work with both synchronous and asynchronous code.
Syntax
-
UseXMLDirective :
-
use xml ListExpressionallowIn
ShockScript: Definitions
Definitions, except interface methods and enum variants, use the internal namespace by default. Interface methods use public unless meta is specified.
Namespace definition
The namespace definition may be primarily used to define a namespace that may be used for protecting or versioning other definitions and XML data processing.
namespace ns1
namespace ns2 = "http://example.com/2015/product"
In addition, the namespace definition may also be used to define aliases to a package wildcard import, as in:
// com.inexistentninja.kunai.*
namespace kunai = "com.inexistentninja.kunai";
// an alias to the top-level package.
namespace SX = "http://www.sweaxizone.com/2015/shockscript/global";
A namespace definition is allowed to alias to the system meta namespace:
namespace meta = "http://www.sweaxizone.com/2015/shockscript/meta";
Namespaces are allowed to nest within blocks regardless of scope. When inside a class block, contributes a static property.
Syntax
-
NamespaceDefinition :
-
namespace IdentifierName
namespace IdentifierName = AssignmentExpressionallowIn
Semantics
A URI namespace contains at least a colon; namespaces assigned a string literal without a colon will result into an alias to a package wildcard import.
Variable definition
Syntax
varandletare equivalent.constandlet constare equivalent.
Note: The
letandlet constkinds compared tovarandconstare a matter of personal taste. They are introduced in ShockScript since they were proposed in the non-existing ECMAScript 4.
-
VariableDefinitionβ :
-
VariableDefinitionKind VariableBindingListβ
-
VariableDefinitionKind :
-
var
const
let [lookahead ≠ const ]
let const
-
VariableBindingListβ :
-
VariableBindingβ
VariableBindingListβ , VariableBindingβ
-
VariableBindingβ :
-
TypedPattern VariableInitializationβ
-
VariableInitializationβ :
-
«empty»
= AssignmentExpressionβ
Function definition
Syntax
-
FunctionDefinition :
-
function FunctionName TypeParametersopt FunctionCommonallowIn
Semantics
A normal function definition that belongs to the meta namespace and whose name equals invoke is implicitly marked static.
Function name
Syntax
-
FunctionName :
-
IdentifierName
get [no line break] IdentifierName
set [no line break] IdentifierName
FunctionName is used inside FunctionDefinition.
function f(): void {}
function get x(): double (impl.x)
function set x(v: double): void { impl.x = v }
TypeParameters may not appear in a function definition defining a getter, setter or constructor.
Function body
Syntax
function f():double 10
function f():void {
// code
}
-
FunctionCommonβ :
-
FunctionSignature
FunctionSignature [lookahead ∉ { { }] [inline, or in a greater indentation, or lookahead = **(**] AssignmentExpressionβ
FunctionSignature Block
Function signature
Syntax
-
FunctionSignature :
-
( Parameters ) ResultType
-
ResultType :
-
«empty»
: TypeExpression
Parameter list
Syntax
-
Parameters :
-
«empty»
NonemptyParameters
NonemptyParameters ,
-
NonemptyParameters :
-
Parameter
Parameter , NonemptyParameters
RestParameter
-
Parameter :
-
TypedPattern
TypedPattern = AssignmentExpressionallowIn
-
RestParameter :
-
... TypedPatternopt
Class definition
Syntax
Nested classes are allowed; however, classes are only allowed in package blocks and top-level region. When nested in another class, contributes a static property.
-
ClassDefinition :
-
class IdentifierName ClassNullabilityopt TypeParametersopt Inheritance Block
-
ClassNullability :
-
!
-
TypeParameters :
-
. < TypeParameterList ParameterizedGreaterThan
-
TypeParameterList :
-
TypeParameter
TypeParameterList , TypeParameter
-
TypeParameter :
-
Identifier
Semantics
ClassNullability is currently ignored as exclamation ! is the default for every class.
Class inheritance
Syntax
-
Inheritance :
-
«empty»
extends TypeExpression
implements TypeExpressionList
extends TypeExpression implements TypeExpressionList
Enum definition
Syntax
Nested enumerations are allowed; however, enumerations are only allowed in package blocks and top-level region. When inside a class block, contributes a static property.
-
EnumDefinition :
-
enum IdentifierName TypeParametersopt Block
Interface definition
Syntax
Interfaces may nest inside classes; outside of classes, interfaces are only allowed in package blocks and top-level region. When inside a class block, contributes a static property.
The interface block must only contain function definitions, which may only contain certain annotations: meta-data and the generic and meta attributes.
-
InterfaceDefinition :
-
interface IdentifierName ClassNullabilityopt TypeParametersopt ExtendsList Block
Semantics
ClassNullability is currently ignored as exclamation ! is the default for every interface.
Interface inheritance
Syntax
-
ExtendsList :
-
«empty»
extends TypeExpressionList
Type definition
A type definition is either used to define an alias to an existing type or used to define a variant inside an algebraic enum.
Syntax
type M = Map.<double, double>
// inside algebraic enums
type X(n : decimal)
Alias type definitions are allowed anywhere. When inside a class block, contributes a static property.
Variant type definitions are only allowed directly in the block of an algebraic enum.
-
TypeDefinitionω :
-
type IdentifierName TypeParametersopt = TypeExpression Semicolonω
type AlgebraicVariantName ( Parameters ) Semicolonω
type AlgebraicVariantName ( Parameters ) Blockopt
-
AlgebraicVariantName :
-
IdentifierName
AlgebraicVariantName . IdentifierName
Package definition
Syntax
-
PackageDefinition :
-
package Block
package [no line break] PackageName Block
A PackageDefinition may be used in a Program before any Directive that is not a PackageDefinition is used.
Package name
Syntax
-
PackageName :
-
Identifier
PackageName . IdentifierName
Program definition
Syntax
-
Program :
-
Directives
PackageDefinition Program