Skip to content
hhh edited this page Jul 18, 2022 · 24 revisions

Basics

Syntax

See Syntax.

Data Types

See DataTypes.

Variables

Variables are used to store specific values. In hxs, variables are defined using syntax pattern a = b:

myName = 'hxs';

Variable names can consist of arbitrary letters, underscores(_) and digits, but they can't start with digits.

After variables are defined, they can be accessed using their names:

str = 'hello world';
print(str);

To check the existence of specific variables, use built-in function exist, which receives a variable name and returns a boolean telling whether the variable exists:

print(exist(#null));        "Prints `true`.";
print(exist(#undefined));   "Prints `false`.";

To delete a variable, use delete:

str = 'blahblah';
delete(#str);

Functions

To create a function, use syntax @(...) { ... }:

greet = @(prefix, name = 'world') {
    print(prefix, ', ', name, '!');
};

or simpler syntax @funcName(...) { ... }:

@greet(prefix, name = 'world') {
    print(prefix, ', ', name, '!');
};

Here, arguments are defined in the paretheses as a list of words. You can also use argName = defaultValue to set defaults.

What's more, you can use syntax restArgs... to create a rest argument which creates an array containing all the arguments that follows:

@sum(x, rest...) {
    s = x;
    if (sizeOf(rest) > 0) {
        s += sum(...rest);
    };
    return(s);
};
sum(1, 2, 3, 4, 5)

Functions can be invoked in two ways:

  1. Using a pair of paratheses containing arguments:

    greet('hello', 'hxs');
    greet('hello');
  2. Using a pair of brackets containing a code block which internally creates a callback function and pass it to the function as the only argument:

    if (true) {
        print('Yes! Process controls are also implemented using functions!');
    };

Inside each function, there are some pre-defined variables:

Variable Name Description
this A value provided by invoker.
arguments An array containing all arguments.
return A function which enables you to return some value and/or stops the execution of the function.
forward See below.

Forward Variables

By default, functions have separate variable scopes and all operations inside functions can't modify outside variables. For example,

x = 0;
@foo() {
    x = 1;
    str = 'hi';
};
foo();

Here, the variable x outside function foo is always 0, no matter foo has been invoked or not, and variable str never exists outside function foo. (Of course, x is 1 inside function foo.)

If you want some variables to be available outside a function, you can use forward function to forward these variables to the outside: (This is done immediately and undefined variable names result in errors.)

x = 0;
@foo() {
    x = 1;
    str = 'hi';
    forward([#x, #str]);
};
foo();

Now, after function foo has been executed, variable x becomes 1 and str becomes available both inside and outside the function.

Expressions

Expressions are actually simplified functions. They are created using (argList...) => ( expression ) syntax and this syntax creates real functions in deed. The difference is that only arguments and this is available inside and the evaluation result of the expression body is returned directly.

Expressions are useful when you just want to create a simple function which only does some calculation and returns the result. For example,

double = (x) => (x * 2);
a = [0, 1, 2];
b = a:map(double);
"b becomes [0, 2, 4] now.";

Optional arguments (with defaults) are also supported in expressions.

Arrays

Arrays are commonly used to store a sequence of values. To create an array, use braces, Array or Array.create:

arraySize = 3;
initValue = 0;
print([0, 1, 2]);
print(Array('a', 'b', 'c'));
print(Array.create(arraySize, initValue));

Then, use index access operation to access values at specific index: (Indices start from 0 and negative ones mean counting from the last element.)

myArray = ['a', 'b', 'c'];
print(myArray[0]); "Prints 'a'.";
print(myArray[-1]); "Prints 'c'.";

And use Array.* functions to modify your array. (See Array.)

Dicts

Dicts(Dictionaries) are used to store paired values, where each key(string) maps to a specific value(anything).

You can create dicts using { key -> value, ... } syntax:

myDict = {
    #a -> 0,
    #b -> 'blahblah',
};

There are many built-in dicts, like namespace Dict, and you can also use Dict to create a dict:

anotherDict = Dict([
    [#a, 0],
    [#b, 'blahblah'],
]);

After a dict is created, use property access syntax to modify it:

myDict.baz = ['a', 'b', 'c'];
print(myDict.a);
print(myDict[#b]);

See Dict for more APIs.

Magic Methods

Many operators are typically used on operands of certain types. However, dicts can also participate in most operations by defining correspoding magic methods. For example,

x = {
    #value -> 1,
    #__plus -> (value) => (this.value + value),
    #__opposite -> () => (-this.value),
};

print(x + 1);   "2";
print(-x);      "-1";

For unary operators, no arguments are provided for the magic method. But for binary operators, the magic method will receive two arguments: the first argument is the other operand; the second argument is a boolean representing whether this dict is the right operand.

x = {
    #value -> 1,
    @__minus(y, right) {
        if (right) {
            return(y - this.value);
        } (true) {
            return(this.value - y);
        };
    },
};

A full list of magic methods (for operators) and corresponding operations is available here:

magic method operation
__plus a + b
__minus a - b
__multiply a * b
__divide a / b
__mod a % b
__pow a ** b
__opposite -x
__bitAnd a & b
__bitOr a | b
__bitXor a ^ b
__bitNot ~x
__leftShift a << b
__rightShift a >> b
__unsignedRightShift a >>> b
__and a && b
__or a || b
__not !x
__nullOr a ?? b
__gt a > b
__gte a >= b
__lt a < b
__lte a <= b
__equal a == b
__notEqual a != b

Invocable Dicts

__invoke is a special method which makes a dict invocable.

For example,

foo = {
    @__invoke(msg) {
        print('foo.__invoke is invoked: ', msg);
    },
};

foo('hello!'); "foo.__invoke is invoked: hello!";

Property Handlers

These magic methods can be used to handle the interaction with dict properties:

Magic Method Description
__get(key) Handles dict[key].
__set(key, value) Handles dict[key] = value.
__has(key) Handles property checks. (e.g., in Dict.unpack.)
__keys() Handles property key access. (e.g., in Dict.unpack.)
__remove(key) Handles property removal. (e.g., in Dict.remove.)

With these magic methods, you can create a Map class:

Map = Class({
    @__init() {
        this.keys = [];
        this.values = [];
    },
    #__keys -> () => (this.keys),
    @__has(key) {
        return(this.keys:includes(key));
    },
    @__remove(key) {
        index = this.keys:indexOf(key);
        if (index !== -1) {
            this.keys:remove(index);
            this.values:remove(index);
        };
    },
    @__get(key) {
        index = this.keys:indexOf(key);
        if (index === -1) {
            return(null);
        } (true) {
            return(this.values[index]);
        };
    },
    @__set(key, value) {
        index = this.keys:indexOf(key);
        if (index === -1) {
            this.keys:push(key);
            this.values:push(value);
        } (true) {
            this.values[index] = value;
        };
    },
});

And use it like this:

map = Map();
map[#foo] = 'bar';
map[map] = map;         "Map anything!";

print(map[#foo]);
print(map[map]);

remove(map, map);       "Remove non-string keys,";
dict = {}:assign(map);  "and assign the map to a dict!";
print(dict.foo);

map:unpack([#foo]);     "Or even unpack it!";
print(foo);
Clone this wiki locally