_______________________________________________________________________________________

1

The KScr Language

Definition of the KScr Language

comroid

PIC

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

Contents

1 Preamble
 1.1 Syntax
  1.1.1 Naming conventions
  1.1.1.1 Package Names
  1.1.1.2 Type Names
  1.1.1.3 Method Names
 1.2 Differences to ’classical OOP languages’
  1.2.1 The null-Literal
2 Source Model
 2.1 Package Declaration
 2.2 Imports and Static Imports
 2.3 Modifiers
  2.3.1 Accessibility Modifiers
  2.3.2 Other Modifiers
 2.4 Classes
  2.4.1 Class Types
  2.4.1.1 class-Type
  2.4.1.2 enum-Type
  2.4.1.3 interface-Type
  2.4.1.4 annotation-Type
  2.4.2 Type Generics
  2.4.2.1 n-Generic
  2.4.2.2 Listing Generic
  2.4.3 extends and implements inheritance setters
 2.5 Class Members
  2.5.1 Static Initializer
  2.5.2 Constructors
  2.5.3 Methods
  2.5.4 Properties
3 The KScr VM
 3.1 Built-in Types
  3.1.1 interface void
  3.1.1.1 Method void#toString(short alt)
  3.1.1.2 Method void#equals(void other)
  3.1.1.3 Property void#Type
  3.1.1.4 Property void#InternalID
  3.1.2 class num<T>
  3.1.2.1 class int<n = 32>
  3.1.3 class str
  3.1.4 class object
  3.1.5 class array<T>
  3.1.6 class tuple<T...>
  3.1.7 class enum<T>
  3.1.8 class type<T>
  3.1.9 interface pipe<T...>
  3.1.10 class range
 3.2 Literals
  3.2.1 null
  3.2.2 Numeric Literals
  3.2.3 String Literals
  3.2.3.1 Interpolation
  3.2.4 Array Literals
  3.2.5 Tuple Literals
  3.2.6 Other Literals
  3.2.6.1 stdio
  3.2.6.2 endl
 3.3 Native Methods
  3.3.0.1 Usage Example
 3.4 Implicit Conversions
 3.5 Implementing Operators
 3.6 Property Caches
 3.7 Lambdas
  3.7.1 Anonymous Lambdas
  3.7.2 Method Reference Lambdas
 3.8 Pipe Functionality
  3.8.1 Passive Pipes
  3.8.2 Active Pipes
4 KScr Build System
 4.1 Build Files

1 Preamble

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.

1.1 Syntax

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.

1.1.1 Naming conventions

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:

  1. 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

  2. The name of the project

  3. 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
    

1.2 Differences to ’classical OOP languages’

1.2.1 The null-Literal

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.

2 Source Model

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.

2.1 Package Declaration

At the beginning of every source file, KScr expects a package declaration:

        package org.comroid.kscr;
    

2.2 Imports and Static Imports

Following the package declaration, a list of import statements is

2.3 Modifiers

2.3.1 Accessibility Modifiers

Modifier Function


public Accessible from everywhere
internal Accessible during compilation; compiled to private
protected Accessible from inheritors
private Accessible from inside only
2.3.2 Other Modifiers

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

2.4 Classes

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.

2.4.1 Class Types

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.

2.4.2 Type Generics

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.

2.4.3 extends and implements inheritance setters

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.

2.5 Class Members

2.5.1 Static Initializer

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()

2.5.2 Constructors

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.

2.5.3 Methods

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.

2.5.4 Properties

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.

3 The KScr VM

3.1 Built-in Types

3.1.1 interface void

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.

3.1.2 class num<T>

The base type of all numerics. Contains numeric subtypes:

  1. byte - Type-alias for int<8>

  2. short - Type-alias for int<16>

  3. int<n = 32>

  4. long - Type-alias for int<64>

  5. float

  6. 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.

3.1.3 class str

The type of all strings.
A string represents an array of characters.

3.1.4 class object

The base type of all non-built-in objects.
Every foreign class that has no explicit extends definition implements this type implicitly.

3.1.5 class array<T>

The type of arrays. The type Generic T defines the type of the array.
Writing array<str> is the same as writing str[].

3.1.6 class tuple<T...>

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.

3.1.7 class enum<T>

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.

3.1.8 class type<T>

The type of all types. The type Generic T defines the actual type that is defined by this type.

3.1.9 interface pipe<T...>

The type of pipes. The type Generic T defines the type of data that is handled by this pipe.

3.1.10 class range

The type of ranges. Ranges are invoked with a tilde: start end

3.2 Literals

3.2.1 null

The null-Literal. Is always of type void.

3.2.2 Numeric Literals

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;
    

3.2.3 String Literals

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"
    

3.2.4 Array Literals

Unimplemented.

3.2.5 Tuple Literals

Unimplemented.

3.2.6 Other Literals

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.

3.3 Native Methods

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;
            }
        }
    

3.4 Implicit Conversions

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.

3.5 Implementing Operators

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)
    

3.6 Property Caches

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.

3.7 Lambdas

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.

3.7.1 Anonymous Lambdas

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.

3.7.2 Method Reference Lambdas

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;
    

3.8 Pipe Functionality

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.

3.8.1 Passive Pipes

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;
    

3.8.2 Active Pipes

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;
    

4 KScr Build System

4.1 Build Files