SubScript v0.2 - Language definition

About

SubScript is a dynamic scripting language that aims at quick and flexible scripting. The name is based upon the fact that (sub)scripts can be defined within scripts and stored in variables for later execution. It is also possible to compound scripts on the fly. The current interpreter is stack based and executes scripts directly.

Language characteristics

The list below shows a few (unordered) characteristics of the SubScript language:
* Functions do not have a fixed argument count.
* No return keyword, inserting a value is enough.
* Scripts can be build dynamically during execution.
* No garbage collection, objects have to be freed manually.
* The last statement does not require a statement separator.

Basic elements

Comment

SubScript only knows single line comments by using the comment sign #.
# This is comment

Calculation operators

SubScript can be used as a calculator as follows.
54 + 2 * (3 + 6);
This will result in:
72

Variables

Variables are automatically declared at first use. To declare a variable the assignment operator ''':''' has to be used. Subscript is a weak-typed scripting language so types are automatically set. Please note that a value has to be assigned directly when declaring a variable.
# Define Text variable
name: "John";

# Define Number variable
age: 44;

# Define Boolean variable
bald: true;

# Define Array variable
hobbies: ["golf", "tennis", "boxing"];

# Alter variable
age: 45;
age: + 5; # Adds 5 to age (becomes 50)

# Use of(print) variables
printl(name);
printl(age);
This will output:
John
50

Defines

Defines work the same way as variables, but its value can not change after declaration. Trying to change the value results in an exception.
# Define a value
define PI: 3.14159265;

# Use the definition
printl(2 * PI * 5);
This will output:
31.4159265

Types

Working with Numbers

# Real numbers
number: 3.14;
number: * 2 * 5;

# Integer numbers
number: 125;
number: 0x7D; # 125 in hexadecimal
number: 0b01111101; # 125 in binary

a: 0b1010;
b: 0b0101;
c: a | b; # a OR b, results in 0b1111
c = 0b1111;
This will result in:
true

Working with Text

# Define a piece of text
text: "Hello";

printl(text); # Prints "Hello"

# Since text is a list, we can do the following
printl(text[2]); # Prints: 'l'

# And even this...
foreach(char: text)
{
    # prints every character on a separate line
    printl(char);
}

# Concatenation is easy
text[]: " World!";

printl(text); # Prints: "Hello World!"

# This works as well...
days: 356;
printl(text + " A year has " + days + " days");
This will output:
Hello
l
H
e
l
l
o
Hello World!
Hello World! A year has 356 days

Work with Arrays

# Define an array
even: [2, 4, 6];

# Show second item
printl(even[1]);

# Add an item
even[]: 9;

# Alter the fourth item
even[3]: 8;

# Show last item
# Negative indexes start at the end of the array
printl(even[-1]);

# Define new array
uneven: [1, 3, 5, 7, 9];

# Merge them into a new array (just add the arrays)
numbers: even + uneven;

# remove the first and last item (just substract the arrays)
numbers: numbers - [numbers[0], numbers[-1]];

# Show all items
foreach(item: numbers)
{
    printl(item);
}

# Multiple dimensions
array: [12, "pigs", [true, 42], numbers];

# Print the second value of the array on the third position
printl(array[2, 1]); # Prints 42

# Rember that we removed the last value from numbers
printl(array[-1, -2]); # Prints 5
This will output:
4
8
4
6
8
1
3
5
7
42
5

Working with Booleans

# Single boolean values
printl(true);
printl(false);

# And now with the NOT operator
printl( !true );

# Composed boolean statements
printl(true & true);
printl(true | false);
printl(false & true);
printl(false & false);

# A total picture
printl( !(false | (true & false)) );
This will output:
true
false
false
true
true
false
false
true

Working with Scrips

# Create a simple script and store it into a variable
script: { 8 * 8 };

# And its possible to execute them
result: execute(script);
printl(result);

newScript: script + { * 8 }; # This creates the script { 8 * 8 * 8 }
printl(execute(newScript));
This will output:
64
512

Flow control

Selection

If, else if, else

a: 3;
b: 3;

if(a < b)
{
    printl("A is smaller then B");
}
else if(a > b)
{
    printl("A is bigger then B");
}
else
{
    printl("A is equal to B");
}
This will output:
A is equal to B

Switch

drink: "jillz";

switch(drink)
{
    "wiskey", "beer"
    {
        printl("Are you a man?");
    }

    "rose",
    "wine",
    "jillz"
    {
        printl("Are you a woman?");
    }

    other
    {
        printl("What are you?");
    }
}
This will output:
Are you a woman?

Iteration

For loop (depricated!)

# Please note that 'i' is the index variable
# format arguments (start point, end point, step size)
for i: (0, 5, 1)
{
	printl(i);
}

# Reverse loop
# Please not that the step size is always positive
for i: (6, 0, 2)
{
    printl(i);
}

Foreach loop

array: ["Hello", "World"];

# Print all items from an array
foreach(item: array)
{
    printl(item);
}

text: "Hello World";

# Print all characters from a text
foreach(char: text)
{
    printl(char);
}

# Print range of numbers (replaces for-loop!)
foreach(number: range(3, 5))
{
    printl(number);
}
This will output:
Hello
World
H
e
l
l
o
 
W
o
r
l
d
3
4
5

While loop

index: 0;

while(index < 5)
{
    printl(index);
    inc(index);
}
This will output:
0
1
2
3
4

Do loop

index: 0;

do
{
    printl(index);
    inc(index);
} (index < 5);
This will output:
0
1
2
3
4

Breaking out

numbers: [32, 79, 34, 93];
find: 34;
index: 0;
 
foreach(i: range(0, length(numbers) - 1))
{
    if(numbers[i] = find)
    {
        index: i;
        break;
    }
}
 
printl(index);
This will output:
2

Functions

Creation and usage

# Define a simple function
function hello()
{
    # Values that are not used as input or stored into
    # a variable will be returned
    "Hello World";
}

# Use the function (it will print the text "Hello World")
printl( hello() );

# Define a function with arguments
function random(min, max)
{
    # Make new number
    min + round(rand() * (max - min));
}

# Call the function
number: random(1, 10);
printl(number);

Flexible arguments

function bar()
{
    # This function has no argument restriction

    printl("Argument count: " + length(args));

    foreach(arg: args)
    {
        printl(arg);
    }
}

bar("Hello", "World");

function foo(a, b)
{
    # This function requires at least two arguments

    printl("a = " + args[0]);
    printl("b = " + args[1]);
}

foo(12, 15);

Recursion

function factorial(n)
{
    if(n > 1)
    {
        n: * factorial(n - 1);
    }

    n;
}
 
printl(factorial(10));

Classes and objects

Creation and usage

# Define a class
class Animal
{
    # Define attributes (always public)
    type: "unknown";
    name: "unknown";
	
    # Constructor
    function init(type, name)
    {
        this.type: type;
        this.name: name;
    }
	
    function getName()
    {
        name;
    }
}

# Array of pet objects
pets:
[
    # Create objects
    Animal("Bird", "Tweety"),
    Animal("Fish", "Nemo"),
    Animal("Dog", "Lassie")
];

# Print all pets
foreach(pet: pets)
{
    printl(pet.type + ": " + pet.getName());
}

Object relationship

class Item
{
    # Textual name of the item
    name: "";
    
    # Constructor
    function init(name)
    {
        this.name: name;
    }
    
    # Return the name of the item
    function getName()
    {
        name;
    }
}

class Storage
{
    # Array of item objects (starts empty)
    items: [];

    # Adds an item to the array
    function addItem(item)
    {
        items[]: item;
    }
    
    # Returns the name of the last item
    function getLastItemName()
    {
        # Retrieve the last item object and return its name
        items[-1].getName();
    }
}

# Create storage object
storage: Storage();

# Add items to the storage
storage.addItem(Item("Box"));
storage.addItem(Item("Shoe"));
storage.addItem(Item("Banana"));

# Show the name of the last item added
storage.getLastItemName();

Single Inheritance

class A
{
    foo: "bar";
}

class B (A)
{
    bar: "foo";
}

b: B();

b.foo + " " + b.bar;

Multiple Inheritance

class A
{
    a: "AAA";
}

class B
{
    b: "BBB";
}

class C (A, B)
{
    c: "CCC";
}

c: C();

printl(c.a + c.b + c.c);

Overriding

class Item
{
    name: "";
    weight: 0;

    # Constructor
    function init(name, weight)
    {
        this.name: name;
        this.weight: weight;
    }
}

class Product (Item)
{
    quantity: 0;

    # Override the parents constructor
    function init(name, weight, quantity)
    {
        this.name: name;
        this.weight: weight;
        this.quantity: quantity;
    }
}

item: Item("Shoe", 12);
product: Product("Drill", 45, 3);

Namespaces

Creation and usage

# Define a namespace
namespace math
{
    define PI: 3.14159265;

    function random(min, max)
    {
        min + round(rand() * (max - min));
    }
}

# Call function
number: math.random(2, 8);

diameter: 8;
perimeter: round(diameter * math.PI);

printl(number);
printl(perimeter);

Root elements

namespace space
{
    count: 15;
}

count: 12;

printl(count + space.count);

Global

namespace space
{
    global define COUNT: 14;
	
    global opened: false;
	
    global function done()
    {
    	printl("Done");
    }
}

printl(COUNT);
done();

Exception handling

Catch exceptions

try
{
    printl(unknownVariable);
}
catch(e)
{
    printl("Message: " + e.message);
    printl("Line: " + e.line);
    printl("Position: " + e.position);
}

Throw exceptions

try
{
    exception("Nothing to do!");
}
catch(e)
{
    printl("Oops... " + e.message);
}

Including scripts

include("external_script.subs");

externalFunction();

Extending scopes

# Define class
class Storage
{
	items: [];

	function addItem(item)
	{
		items[]: item;
	}
}

# Create and test object
storage: Storage();
storage.addItem(12);

# Open the scope of the storage object
use(storage)
{
    # Call internal functions
    addItem(13);
    addItem(14);

    # Add a function
    function printItems()
    {
        foreach(item: items)
        {
            printl(item);
        }
    }
}

# Call the added fuction
storage.printItems();

Script weaving

Simple weaving

include("logger.subs");

# Define script for logging exceptions
# Scripts can be inserted (weaved) on multiple points for optimal reuse
# This is more efficient then calling a function within a separate catch script
logException:
{
    Logger.log("Error: " + e.message + " on line " + e.line + ", position " + e.position);
};

try
{
    12 + integer("14");
}
catch(e) logException; # Weave script

try
{
    12 + integer(true);
}
catch(e) logException;  # Weave script again

Weaving extended scripts

include("logger.subs");

# Define script for logging exceptions
logException:
{
    exceptionMessage: "Error: " + e.message + " on line " + e.line + ", position " + e.position;
    Logger.log(exceptionMessage);
};

# Extend the log script with a print function
# Extended scripts form a single scope, so the exceptionMessage variable is available
logAndPrintException: logException +
{
    printl(exceptionMessage);
}

try
{
    12 + integer("14");
}
catch(e) logException; # Weave log only script

try
{
    12 + integer(true);
}
catch(e) logAndPrintException;  # Weave log and print script

List comprehension

Since list comprehension is not more than syntactic sugar, it is not (yet) implemented in the language. In case of need it is easy to simulate this functionality by using the power of sub scripts.
function listComp(output, input, predicate)
{
    # Create list to store the result
    result: [];

    # Write the correct code that is parsable
    # Please note that the operation is nested into
    # the comprehension (instead of concatenated)
    operation: { result[]: } + output;
    comprehension: { if( } + predicate + { ) operation; };

    # Create new list
    foreach(x: input) comprehension;

    # Return the result
    result;
}

# Lets test it with the following comprehension:
# S = { x * x | [1..100], x rem 3 = 0 }
list: listComp({x * x}, range(1, 100), {x % 3 = 0});

# Show results
foreach(item: list)
{
    printl(item);
}
Please note that the listComp function rewrites the comprehension (in this specific case) as follows.
foreach(x: input)
{
    if(x % 3 = 0)
    {
        result[]: x * x;
    }
}
The same function can be used with arrays that have more then one dimension.
define NAME: 0;
define GRADE: 1;

grades:
[
    ["Jim", 1.6],
    ["John", 7.8],
    ["Mary", 5.2],
    ["Jeff", 5.5],
    ["Ann", 6.7],
    ["Wendy", 3.8]
];

passed: listComp({ [x[NAME], round(x[GRADE])] }, grades, { x[GRADE] > 5.4 });

printl("" + length(passed) + " students passed:");

foreach(result: passed)
{
    printl(" - " + result[NAME] + ": " + result[GRADE]);
}