Vyse is a relatively fast, dynamically typed scripting language intended for embedding in C++ applications, like game engines.
Vyse supports the following operators, from highest to lowest precedence:
+----------+-------------------------------+---------------+
| Operator | Name/Description | Associativity |
+----------+-------------------------------+---------------+
| () | Call | Left-to-right |
| [] | Computed Member access | |
| . | Member access | |
+----------+-------------------------------+---------------+
| +- | Unary Plus/Minus | |
| !~ | Logical Not, Binary Not | |
| typeof | Type of | |
| # | Length | |
+----------+-------------------------------+---------------+
| ** | Exponent | Left-to-right |
+__________+_______________________________+_______________+
| * / % | Mult / Div / Mod | Left-to-right |
+----------+-------------------------------+---------------+
| + - | Add / Sub | Left-to-right |
+----------+-------------------------------+---------------+
| << >> | BitShift left, Bitshift right | Left-to-right |
+----------+-------------------------------+---------------+
| < <= | Comparison | Left-to-right |
| > >= | Greater, Less | |
| | Less Equal, Greater Equal | |
+----------+-------------------------------+---------------+
| == != | Equals, NotEquals | Left-to-right |
+----------+-------------------------------+---------------+
| & | Bit And | Left-to-right |
+----------+-------------------------------+---------------+
| | | Bit Xor | Left-to-right |
+----------+-------------------------------+---------------+
| | | Bit Or | Left-to-right |
+----------+-------------------------------+---------------+
| && | Logic-and | Left-to-right |
+----------+-------------------------------+---------------+
| || | Logic-or | Left-to-right |
+----------+-------------------------------+---------------+
| <<< | Append | Left-to-right |
+----------+-------------------------------+---------------+
| ?: | Ternary Operator (misfix) | Right-to-left |
+----------+-------------------------------+---------------+
| = | Assignment and compound | Right-to-left |
| += *= | assignment operators | |
| -= %= | | |
| **= | | |
+----------+-------------------------------+---------------+
Variables behave very similar to javascript. Can be declared with let
and const
,
and can hold values of any type.
let mynum = 123; -- mutable declaration is done with 'let'
mynum = 456; -- assignment with '='
const my_other_num = 1; -- variables declared with `const` cannot be reassigned to.
Vyse supports the following control flow statements: if-else if-else, for, while.
If statements are very straightforward:
if condition1 {
print("1")
} else if condition2 {
print("2")
} else {
print("3")
}
For loops in vyse come with an tiny bit of extra power. The general for loops look like this:
for i = 1, 10 {
print("i = ", i)
}
You can compare this loop with the following C code that does the very same thing:
for (int i = 0; i < 10; i++) {
printf("i = %d\n", i);
}
If you want the loop to go up by some other amount every step, then you can specify a third parameter:
for i = 1, 10, 2 {
print("i = ", i)
}
Similarly, to count down to 0 from some number, you could use 10, 0, -1
as the loop parameters instead.
For iterating over an array, this could be one way:
for i = 1, #my_array {
print(my_array[i])
}
Wherein the value of i
starts at 1 and goes up by 1 until it's as big
as the size of the array.
However, Vyse provides a more convienient way to do it when you're
concerned with the value at the i
th index and not i
itself:
for item in my_array {
print(item);
}
Functions are declared using the fn
keyword and like most languages,
called using the ()
operator.
fn fib(x) {
if x <= 1 return fib_base
return fib(x - 1) + fib(x - 2);
}
Alternatively, once can declare them like variables using let
or const
keywords.
let greet = fn (name) {
return "Hello !" .. name;
}
The ..
operator is used to concatenate strings.
All functions are first class values in Vyse This means functions can be passed around and used just like any other value.
Functions are can capture their surrounding environment. In this sense, all functions are closures.
fn make_greeter(name) {
const greeting = ". Good morning!"
return fn() {
return "Hello, " .. name .. greeting;
}
}
const bob_greeter = make_greeter("Bob");
bob_greeter();
Output: Hello, Bob. Good morning!
Functions can be called with less or more number of arguments than mentioned in the definition.
- When called with fewer arguments, the missing arguments are assumed to be
nil
. - When called with more arguments, the extra parameters are simply ignored.
For example:
fn log(x) {
print(x)
}
log(10); -- 10
log(); -- nil
log(5, 10); -- 5
Functions can optionally contain a rest
parameter, which can stand for any
number of parameters. Note that the rest
parameter must be the last in
parameter list.
fn log(xs...) {
for x in xs {
print(x)
}
}
log(1, 2, 3)
Output:
1
2
3
Objects are a data structure that basically behave as a hashtable. You can think of them as key value pairs just like objects in javascript or tables in Lua.
const Tom = {
name: "Tom",
age: 19,
race: "cat",
};
print(Tom.name); // Tom
Objects can be indexed with any value. Strings, numbers, booleans, functions
and even other tables are all possible key types for an object.
The only exception to this rule is the nil
value.
print(Tom["age"]); // 19.
let my_obj = { a: 1, b: 2 };
Tom[my_obj] = 123;
print(Tom[my_obj]); // 123
To remove an key value from a table, simply set
it's value to nil
.
print(Tom.age) -- 19
Tom.age = nil -- delete key 'age'
print(Tom.age) -- nil
Objects can also have methods, which can be called with the :
operator.
For any method bound to an object, there should always be an extra first
parameter. By convention, the first parameter is called self
. But you may
choose to call it by any name.
const Cat = {
name: "Tom",
sound: "Meow",
meow() {
print(self.sound .. "!")
}
}
Cat:meow() -- Meow!
Note that calling a method with the :
operator simply means the first
parmeter passed to the function is the caller itself. In this case, it is
exactly identical to saying Cat.meow(Cat)
.
An object can have parent objects, when a certain property is not found in an object itself, the parent is queried for the property.
const t_parent = { a: 1, b: 2 };
const t_child = { a: 3 };
print(t_child.a, t_child.b); -- 3, nil
setproto(t_child, t_parent); -- set t_child's prototype to t_parent
print(t_child.a, t_child.b); -- 3, 2
This design is inspired by Lua and can be used to simulate some OOP features, like method overriding.
Many vyse operators can be overloaded to perform different actions. The overloading methods must exist somewhere up in the parent object hierarchy. Eg -
const Point = {
init(x, y) {
const t_point = { x: x, y: y }
return setproto(t_point, self)
}
}
const a = Point:init(10, 10)
const b = Point:init(3, 4)
let c = a + b
Upon running the above code, we get:
ERROR: Bad operand types for operator '+': 'table' and 'table'.
The above code will throw an error since we can't add two tables.
To remedy the error, we introduce a magic method that overloads the +
operator:
Point.__add = fn (a, b) {
return Point:init(a.x + b.x, a.y + b.y);
}
const c = a + b;
print(c.x, c.y)
This time, the code successfully outputs 13, 14
.
A table representing all the overloadable operators and the method names is listed below for reference:
Operator | Method | Arity |
---|---|---|
+ | __add | 2 |
- | __sub | 2 |
- (unary) | __unm | 1 |
/ | __div | 2 |
% | __mod | 2 |
** | __exp | 2 |
> | __gt | 2 |
< | __lt | 2 |
>= | __gte | 2 |
<= | __lte | 2 |
! | __not | 1 |
^ | __xor | 2 |
& | __band | 2 |
| | __bor | 2 |
<< | __bsl | 2 |
>> | __bsr | 2 |
~ | __bnot | 1 |
. | __get | 2 |
[] | __index | 2 |
== | __eq | 2 |
!= | __neq | 2 |
.. | __concat | 2 |
() | __call | any |
(tostring) | __str | 1 |