SYXZyt / Astral

Astral is a scripting language designed for my own game engine, but is not limited to just that

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Astral Scripting Language

Astral is a dynamic typed scripting language designed to be easy to use and integrate into other software. The language features a syntax which has mainly been inspired by JavaScript and C. There are no classes but structs can be used and passed by reference.

Important Notice

Astral and its runtime are still currently in development. Many of the features shown here may not be implemented yet.

Data Types

Type Description
Number 32-bit floating point number
String Sequence of characters
Void Void is the lack of any data. Astral equivalent of null or None
Object Collection of types bonded together in a struct
Function Object which can be called as a function
Extern Function Object which can be called as an external function (Native C++)

Planned Future Types

Type Description
Array Dynamically sized list of data
Char Single character. Will be used when reading or writing to a string at a specified index

Implicit Boolean Conversion

Astral will implicitly convert some types to booleans when used in an if statement. In Astral, booleans are simply represented as a number, where true is non-zero, like C. Void if passed into an if statement will always return false, and a struct will always return true. This is implemented to allow C-like null checking for pointers. In C, you can do this to check if an object has been defined.

void* myobj = NULL;
if (myobj)
	printf("My Object Exists\n");
else
	printf("My Object Doesn't Exist\n");

and Astral will allow this as if the object has been defined as a struct, it will return true, otherwise Void which will return false.

Syntax

Astral follows a simple syntax. A program will be made up of global variable definitions, structure definitions and functions. Global variables are variables which can be access anywhere within the program, regardless of scope.

Global variables will be defined by using the global keyword. By default, they will store void. They should be initalised in the main method or before use. Functions can be defined using the func keyword. Following the dynamic typing of Astral functions do not have any explicit return type. Additionally, the language has not function overloading, although this is planned in the future.

One problem that is important to note with Astral, is that all conditions in an if statement are ran. Take this for example

let obj = create();
if (obj && obj.member)
	...

This code is fine, until obj is void. You might think it will be ok since we are checking if the object exists, but Astral will run both expressions before calculating the &&. If the object is void then this code will result in a void reference. The solution would be this.

let obj = create();
if (obj)
{
	if (obj.member)
		...
}

Bitwise

Astral does not have any bitwise operators, however, the maths library contains functions which can perform these operations.

using astral.io;
using astral.math;
using astral.string;

let a = 11;  //1011
let b = 10; //1010

println("a");
println(binary_from_num_w(a, 4));

println("b");
println(binary_from_num_w(b, 4));

println("a & b");
println(binary_from_num_w(and(a, b), 4));

println("a | b");
println(binary_from_num_w(or(a, b), 4));

println("a ^ b");
println(binary_from_num_w(xor(a, b), 4));

println("~a");
println(binary_from_num_w(not(a), 4));

println("~b");
println(binary_from_num_w(not(b), 4));

println("a << 1");
println(binary_from_num_w(shift_l(a, 1), 4));

println("b << 1");
println(binary_from_num_w(shift_l(b, 1), 4));

println("a >> 1");
println(binary_from_num_w(shift_r(a, 1), 4));

println("b >> 1");
println(binary_from_num_w(shift_r(b, 1), 4));

which outputs

a
1011
b
1010
a & b
1010
a | b
1011
a ^ b
0001
~a
0100
~b
0101
a << 1
0110
b << 1
0100
a >> 1
0101
b >> 1
0101

Examples

Hello World

func Main()
{
	println("Hello, World!");
}

FizzBuzz

func Main()
{
	let f = 3;
	let b = 5;

	for (let i = 1; i <= 100; i += 1)
	{
		let output = "";

		if (i % f == 0)
			output += "Fizz";
		
		if (i % b == 0)
			output += "Buzz";

		//If we didn't match either case, just print the number
		if (output == "")
			output = i;

		Println(output);
	}
}

Value v References

By default, Astral will pass all parameter by value unless otherwise specified. Passing by value will mean that the local variables in a function will be copies of the original data. If the function should change the original data then a reference should be used. A reference is a link to another variable so that if one instance is changed, then all instances are changed. References are mark with the & operator.

let x = 0;
let y = &x; //Y is now linked to x. Changing x OR y will change the data.
println(x);
y = 10;
println(x); //X now stores 10 even though we changed y

References are also more performant when used as parameters since a copy does not need to be generated, however if you want the re-assurance that the data can not or should not change from the function, do not use a reference. In the future, const references may be implemented. Such a feature could allow a reference to be passed, while blocking its value from being changed.

Some built-in functions will not work with values. An example of this is string_write. This function will write a character to a string at a given index. Since this function is expected to change the underlying data, a reference should be used.

func main(a)
{
	let name = "Astral";

	string_write(name, 0, "O"); //Since it is working on a copy of name, the original value will not change.
	println(name); //Prints 'Astral'

	string_write(&name, 0, "O");
	println(name); //Prints 'Ostral'
}

Aurora Interoperability

Aurora is the game engine I am currently working on. Thanks to the binding system implemented in the Astral runtime, Astral can easily have support added to different programs. This is a sample script for Aurora which will make a light flash on and off each second

global timer;
global active;
global rate;
global owner;

func Start()
{
	timer = 0;
	active = true;
	rate = 1.0;
	owner = PB_Get_Owner_AsLight();
}

func Update(dt)
{
	timer += dt;
	
	if (timer >= rate)
	{
		timer = 0;
		active = !active;
		owner.lightState = active;
	}
}

Compilation Stages

Lexer

The lexer, lexical analyser or tokeniser is the first stage in compilation. It will generate tokens which are sequences of related characters. For example, if the lexer reads the characters f u n c then it knows that those characters are part of the same keyword and so it can make a func token.

Parser

The next stage is parsing which will take in the generated tokens and will use those to create an abstract syntax tree, or AST. This is a tree which will store a representation of the source code. These trees will take into account for operator precedence to easily allow mathematical equations to be executed. The expression 1 + 2 * 3 will be turned into this tree.

 +
/ \
1  *
  / \
  2  3

whereas (1 + 2) * 3 will be turned into this tree.

  *
 / \
 +  3
/ \
1 2

Linker

The linker in Astral does not quite function the same as the C/C++ linker. In Astral, the linkers job is to resolve include statements. These statements will tell the compiler that it wants to use another file and load its code. The linker will take the given filename, try to load it, and run the first two compilation stages on the code. Once a tree has been generated, it will be appended onto the end of the current tree. Also important to the linker is to track which files have already been loaded as to ensure that the same file is not loaded multiple times (similar to pragma once in C++).

Compiler

The final stage of compilation is the compiler or assembler. It will navigate through the AST and generate bytecode. Bytecode is the instructions that the virtual machine or interpreter will execute. These instructions are very simple and will usually consist of, push value onto stack, or add together two values.

Building

Astral uses cmake to build. Many IDE's will work, although I mainly develop on Visual Studio 2022 and CLion, but I have tested it in VS-Code and it works. VS and Clion will automatically build with no issues but VS-Code relies on using commands. You can build by running cmake . and then make. This is for Linux, if you are on windows then you should use Visual Studio.

Linking

Astral is now a static library which means all you need is the .lib file, or the .a on Linux. The astral source does not include an include folder, although you can use the included Python script to make one. Simply call python generate_include.py

About

Astral is a scripting language designed for my own game engine, but is not limited to just that


Languages

Language:C++ 97.7%Language:CMake 1.4%Language:C 0.5%Language:Python 0.4%