_______________________________________________________________________________________
1
The KScr Language
Definition of the KScr Language
comroid
The KScr Language as defined by this document is a compiled high-level language whose goal is to provide runtime optimizations from a different approach.
Team:
Kaleidox
The KScr Language is developed with the idea in mind that there should be versatile and rudimental
features available to write effective, yet compact code.
Its main design idea is to implement polymorphism at a level that allows implementation of
high-quality API to be implemented without repeating code, minimizing time spent writing repetitive
code.
The secondary objective is to allow for runtime optimizations at computation level.
The syntax is designed with two known languages in mind; C# and Java. It implements properties
similar to C# and interfaces and enums similar to Java.
Abbreviations should be always capitalized like their first letter. Exceptions may occurr if an abbreviation is artistically or semantically capitalized, an example type name would be: LaTeXParser.
Package Names Package names should be all lowercase, and chosen with the following pattern in mind:
The domain that the project is released under, reversed. For example, all standard packages start with org.comroid. If the domain is a subdomain, the subdomain name should be included at the end; example org.comroid.api
The name of the project
Possible sub-packages that might be necessary.
An example for a full package name is:
org.comroid.status.api.model
Type Names Type names should be capitalized in camel case. An example would be:
ChannelIDFinder
Method Names Member names should be capitalized in lower camel case. Two example would be:
#findByID() #wsClose() // websocket close
The null-Literal differs in KScr from it’s commonly known functionality.
It does implement all members in void with reasonable default implementations. For example,
null.toString() will always return a string that says ’null’. null.Type will always return void and
null.InternalID will always return 0.
KScr expects source files to have the extension .kscr, and outputs binaries with the extension .kbin.
Source files are not allowed in the source root directory. The binary root directory contains a string
cache.
At the beginning of every source file, KScr expects a package declaration:
package org.comroid.kscr;
Following the package declaration, a list of import statements is
Modifier | Function |
public | Accessible from everywhere |
internal | Accessible during compilation; compiled to private |
protected | Accessible from inheritors |
private | Accessible from inside only |
Modifier | Function |
static | Cannot be invoked dynamically |
final | Cannot be overridden or changed |
abstract | Must be implemented in inheritors |
synchronized | Invocations are synchronized |
native | Must be implemented by a native module |
A class is declared by a class header of the following structure:
<accessibility modifiers> [class type] [class name]<type generics> <inherits> <body>
Note that the class body can be optional when using a semicolon.
A source class can be of different types:
class-Type A normal class that can be instantiated. Allowed modifiers are:
Modifier | Function |
static | Cannot be instantiated and behaves like a Singleton |
final | Cannot be inherited by other classes |
abstract | Cannot be instantiated directly and must be inherited by other classes |
enum-Type An enumeration of runtime-constants that follow a class-like pattern. This type does not allow modifiers.
interface-Type An interface that declares basic structure requirements for implementing classes. Cannot be instantiated directly.
annotation-Type A marker for most components of code. Used for flow control by enforcing rules at compile time, setting markers or carrying information at runtime.
Type Generics are initially defined in a classes header, detailing the name with a postfix. They are surrounded by arrow-brackets <…>:
public class num<T> {}
There is two special kinds of Type Generics;
n-Generic
The n-Generic serves as a type-based declaration for tuple types.
Its usages must be integers, and their value is available at runtime in a semi-static field public final
int n.
Writing string<2> is the same as writing tuple<2, string>
If the n-Generic is defined explicitly, then it is not used as a tuple alias.
Listing Generic
A listing Generic serves as a varargs-Generic.
Its instance is an array of types which can be accessed at runtime in a semi-static field.
The extends and implements keywords follow the detailed class name definition and declare what
classes or interfaces a class inherits.
Both the extends and the implements listing can contain multiple members.
The static initializer is declared by including a static member block in the class.
It is executed after compile-time (or read-time if reading binary) and can modify the members of the
containing class.
static { // initialize class right before late initialization }
The static initializer is compiled to be a method with the header private static final void cctor()
The constructor is used to create a dynamic instance of the class.
If a class extends more than one class, any of which does not support the default constructor, the
constructor must declare all superconstructors like this:
public class Apple extends Fruit, Projectile implements Digestable { public Apple() : Fruit("Apple"), Projectile(); }
Constructors are compiled as Methods named ctor.
A method is a function of a class that can affect the class, compute a result or print "Hello, world!".
Methods are distinguished from properties by their parameter definition, which may be empty.
All methods must have an explicitly defined return type, but they may return void.
Properties are value computation access ports that can either hold a value, or compute it from a
returning body.
If such return bodies contain another property, then the other property is checked for its last update
time. If it has been updated before the calling property has been, then computation is skipped and the
last returned value is returned again. Setting a property updates it, causing all dependent properties to
be computed again on their next access.
The universal base type. Is implemented by all built-in types and can be implicitly cast to everything.
Method void#toString(short alt)
Used to obtain a string representation of the object.
The toString() Method that is present in all objects has one optional parameter; a short that defines
the alternative of the output string. Its default value is 0.
The following values must be returned by different alternatives:
Value | Predefined output string |
0 | A parseable representation of the object |
1 | A name that contains type information |
2 | An undetailed name of the object |
3 | A full undetailed name of the object |
4 | A detailed name of the object |
5 | A full detailed name of the object |
Method void#equals(void other) Used to test two objects for equality. This method is called by the = and ≠ operators.
Property void#Type Used to obtain the exact class instance of an object.
Property void#InternalID Used to obtain the internal ID of the object.
The base type of all numerics. Contains numeric subtypes:
byte - Type-alias for int<8>
short - Type-alias for int<16>
int<n = 32>
long - Type-alias for int<64>
float
double
All subtypes can be used directly, or using the Type Generic T, for example: int<24> == num<int<24»
class int<n = 32>
The int<n> type defines an integer measured by n bytes.
The default value of n is 32; as it is for common integers.
The type of all strings.
A string represents an array of characters.
The base type of all non-built-in objects.
Every foreign class that has no explicit extends definition implements this type implicitly.
The type of arrays. The type Generic T defines the type of the array.
Writing array<str> is the same as writing str[].
The type of tuples. The type Generic T... defines all types of held values in order.
If T.Size == 1, the tuple is of a singular type and might have been invoked by the n-Type-Generic;
T<n>. Size must then be obtained by invoking it.Size.
The type of enums. The type Generic T defines the output type of the enum.
A class enum Codes would be of type enum<Codes> or simply Codes.
The type of all types. The type Generic T defines the actual type that is defined by this type.
The type of pipes. The type Generic T defines the type of data that is handled by this pipe.
The type of ranges. Ranges are invoked with a tilde: start end
The null-Literal. Is always of type void.
A numeric literal may either be a decimal number, a hexadecimal string or a binary string.
Decimals In case it is an irrational decimal, it may be followed by a letter indicating the number type. Supported types are:
0.0f | float |
0.0d | double (Default) |
Hexadecimals A hexadecimal string must be preceded with 0x. Output type will be an int<n> where n is chosen with a radix of 8.
int<8> x = 0x9;
Binaries A binary string must be preceded with 0b. Output type will be an int<n> where n is chosen with a radix of 1.
int<5> x = 0b00101;
A string literal is pre- and superceded by a double-quote " symbol. An escaped double-quote \" can be contained in the string.
Interpolation A string supports interpolation using accolades with Formatter-support with the following syntax:
int hex = 1 << 3; stdio <<- "hex: {hex:X}" // prints "hex: 0x4"
stdio
Constantly represents the program’s standard IO stream.
The held value is of type pipe<str>.
endl
Constantly represents the environment’s standard Line feed style.
The held value is of type str.
KScr supports calls to native C# members. To use this, it is necessary to mark a method in KScr
source code as native, and include a DLL that contains a method attributed with [NativeImpl].
At startup, the directory include/ at the installation base path is recursively scanned for *.dll
assemblies, which are then loaded and scanned for [NativeImpl] attributes.
Usage Example Considering the following KScr class:
package org.comroid.test; public class NativeTest { public static native void callNativeMethod(); }
For successful execution, a C# class must be present at runtime with the following signature:
[NativeImpl(Package = "org.comroid.test", ClassName = "NativeTest")] public class NativeImplementations { [NativeImpl] public static IObjectRef callNativeMethod(RuntimeBase vm, Stack stack, IObject target, params IObject[] args) { return vm.ConstantNull; } }
All built-in types can be implicitly converted to and from each other. For example, trying to assign a str to a variable of type int will first call int.parse() on the string.
Implementing an operator is as simple as adding a method named op<Operator> to a class, where for
binary operators, the first and only parameter denotes the right operand.
For example, implementing a division operator for strings would be as simple as adding the following
method:
public str opDivide(int right)
Any property contains a value that knows when the property was last updated. If a property has a
computed getter, then the getter will only be evaluated if any property used inside the getter has been
updated later than the computed property.
After computation, the result is stored in an internal cache and will be returned if all used properties
have not been updated after the computed property.
Every interface that has exactly one abstract method is a functional interface.
Functional interfaces can be invoked as a lambda instead of an object instance.
An anonymous lambda consists of a parameter denotation in parentheses, followed by a normal arrow
and either an expression or a statement body. An example for an anonymous lambda that implements
an integer addition Func<int, int, int> is:
(x, y) -> x + y; // or, using a statement body: (x, y) -> { return x + y; }
A lambda without parameters is denoted by empty parentheses.
A method reference lambda may be used if the functional interface notation and the referenced method notation match. If the referenced method is non-static, then the first parameter must be assignable to the invoking target object.
// static method reference: Func<int, str> parseInt = int.parse; // dynamic method reference: Func<str> toString = myObject.toString;
KScr supports both an active and passive object pipeline through the pipe<T> interface. These can be used to either handle and parse incoming & outgoing data, or to read and write data finitely to or from an accessor.
Simple examples for passive Pipes are the stdio Keyword and all properties.
Passive Pipes can only be read and written to, by using double-arrow operators.
Additionally, an active pipe operator can be attached to listen for value changes.
// write text to stdout: stdio <<- "text"; // read line from stdin: stdio ->> str line; // call velocityChangeListener() every time ’velocity’ is changed: velocity >>= velocityChangeListener; // or create an active pipe that serves as an event hub: velocityHandler = velocity =>> velocityChangeListener;
An active pipe actively uses callbacks to push values downstream. They can be stored in a variable, and
cascaded for advanced event processing.
For example, parsing TCP data from a Websocket to JSON may look like this:
// parse websocket packets from TCP data websocketData = tcpData =>> parseWebsocketPacket; // parse json from websocket packet body jsonData = websocketData =>> (data -> data.body) =>> parseJson;