Welcome to ARISL (A Rapidly Interfacing Scripting Language), a versatile and user-friendly scripting language that transpiles into C# code. ARISL aims to offer the flexibility and simplicity of dynamic scripting languages while harnessing the power and performance of C#.
- Introduction to ARISL
- Core Syntax of ARISL
- Importing and Using External Libraries
- Future Developments and Encouragement
ARISL is designed to be intuitive, allowing developers to write code quickly and efficiently. It supports a wide range of features, including dynamic variable declarations, structs, interfaces, records, class definitions, etc. With ARISL, you can leverage the vast ecosystem of C# libraries and tools, making it a powerful choice for both small scripts and large applications.
- Dynamic Typing: Variables in ARISL can be declared without specifying a type, making the language flexible and easy to use.
- C# Types: Because the language transcompiles directly into C# we can explictly use C# types placing a
~
in front of a modifier. For example:~int x = 1;
- Lua-style Collections: You can create and manipulate tables in a way similar to Lua, offering a familiar syntax for many developers.
- Interfacing with C# Libraries: You can directly interact with C# types and libraries, integrating seamlessly with existing .NET code.
The ARISL interpreter and transcompiler are at the heart of this language, converting ARISL scripts into executable C# code. This dual functionality ensures that you can both develop and execute your scripts in a streamlined environment.
ARISL includes a Read-Eval-Print Loop (REPL) mode, allowing you to write and test your code interactively. This mode is especially useful for experimenting with new features and debugging code on the fly.
Entering REPL Mode:
To start ARISL in REPL mode, simply run the interpreter without any arguments:
ARISL.exe
Example REPL Session:
ARISL> x = 5
ARISL> Console.WriteLine(x);
5
ARISL allows you to extend its functionality by installing packages, similar to how you would manage dependencies in a typical C# project.
Downloading a Package:
ARISL --download Newtonsoft.Json 13.0.1
Listing Installed Packages:
ARISL --list
Deleting a Package:
ARISL --delete Newtonsoft.Json
Setting Up ARISL:
For convenience, you may want to add the folder containing ARISL.exe
to your PATH environment variable. This will allow you to run ARISL from any command prompt or terminal without needing to specify the full path to the executable.
In ARISL, variables can be declared without specifying a type, making them dynamic by default. If no visibility is specified, variables are public.
Examples:
x = 10; // dynamic type inferred as int
y = "Hello"; // dynamic type inferred as string
z = true; // dynamic type inferred as bool
If you want to specify a type, you can do so using the ~
symbol.
~int a = 10;
~string b = "Hello";
~bool c = true;
Functions in ARISL can be defined using the func
keyword. If the return type is not specified, the function is compiled as dynamic
. This means you can use return statements as you please. Functions can also be asynchronous using the async
keyword.
Examples:
func SayHello() {
Console.WriteLine("Hello, World!");
}
private async func FetchData() {
await Task.Delay(1000);
Console.WriteLine("Data fetched!");
}
ARISL supports typical control structures such as if-else statements, while loops, for loops, and foreach loops.
If-Else Statements:
x = 10;
if x > 5 {
Console.WriteLine("x is greater than 5");
} else if x == 5 {
Console.WriteLine("x is equal to 5");
} else {
Console.WriteLine("x is less than 5");
}
While Loops:
x = 0;
while x < 5 {
Console.WriteLine(x);
x++;
}
For Loops:
for ~int i = 0; i < 5; i++ {
Console.WriteLine(i);
}
Foreach Loops:
numbers = tbl|1, 2, 3, 4, 5|;
foreach ~int num in numbers{
Console.WriteLine(num);
}
ARISL allows you to define classes using the class
keyword. Classes can have fields, properties, constructors, and methods.
Example Class Definition:
class Dog {
public ~string Name ~*~
public ~int Age ~*~
@(~string name, ~int age) { // constructor
Name = name;
Age = age;
}
func Bark() {
Console.WriteLine(Name + " says Woof!");
}
}
dog = new ~Dog("Buddy", 5);
dog.Bark(); // Output: Buddy says Woof!
ARISL includes try-catch-finally statements for error handling.
Example:
try {
x = 10 / 0;
} catch (~Exception e) {
Console.WriteLine("An error occurred: " + e.Message);
} finally {
Console.WriteLine("This will always be executed.");
}
ARISL supports Lua-style tables for creating associative arrays.
Example:
arr = tbl|"x" = 50, "y" = 42|;
Console.WriteLine(arr["x"]); // prints 50
Here's a complete example that combines several features discussed above:
class Dog {
public ~string Name ~*~
public ~int Age ~*~
@(~string name, ~int age) {
Name = name;
Age = age;
}
func Bark() {
Console.WriteLine(Name + " says Woof!");
}
}
async func Main() {
arr = tbl|"x" = 50, "y" = 42|;
Console.WriteLine(arr["x"]); // prints 50
dog = new ~Dog("Buddy", 5);
dog.Bark(); // Output: Buddy says Woof!
x = 0;
while (x < 5) {
Console.WriteLine(x);
x++;
}
await TestAsync();
}
async ~Task func TestAsync() {
await Task.Delay(1000);
Console.WriteLine("Hello World Async!!");
}
Now, let's create a simple Flappy Bird game using Raylib. We'll go step-by-step, explaining our decisions and highlighting the differences between C# and ARISL syntax.
Step 1: Importing Raylib:
dotnet(Raylib-cs);
using Raylib_cs;
Step 2: Setting Up the Window:
func main() {
~int screenWidth = 800;
~int screenHeight = 450;
Raylib.InitWindow(screenWidth, screenHeight, "Flappy Bird");
Raylib.SetTargetFPS(60);
// Other game setup code will go here
Raylib.CloseWindow();
}
Step 3: Defining Game Variables:
func main() {
~int screenWidth = 800;
~int screenHeight = 450;
Raylib.InitWindow(screenWidth, screenHeight, "Flappy Bird");
Raylib.SetTargetFPS(60);
// Game variables
birdX = screenWidth / 4;
birdY = screenHeight / 2;
birdRadius = 20;
~double
birdSpeedY = 0;
~double gravity = 0.5;
jumpStrength = -10;
pipeWidth = 80;
pipeGap = 200;
pipeX = screenWidth;
pipeHeight = Raylib.GetRandomValue(100, screenHeight - pipeGap - 100);
gameOver = false;
score = 0;
// Other game code will go here
Raylib.CloseWindow();
}
Step 4: Handling Game Logic:
func main() {
~int screenWidth = 800;
~int screenHeight = 450;
Raylib.InitWindow(screenWidth, screenHeight, "Flappy Bird");
Raylib.SetTargetFPS(60);
// Game variables
birdX = screenWidth / 4;
birdY = screenHeight / 2;
birdRadius = 20;
~double birdSpeedY = 0;
~double gravity = 0.5;
jumpStrength = -10;
pipeWidth = 80;
pipeGap = 200;
pipeX = screenWidth;
pipeHeight = Raylib.GetRandomValue(100, screenHeight - pipeGap - 100);
gameOver = false;
score = 0;
while !Raylib.WindowShouldClose() {
// Update
if !gameOver {
if Raylib.IsKeyPressed(KeyboardKey.Space) {
birdSpeedY = jumpStrength;
}
birdSpeedY += gravity;
birdY += (~int)<~birdSpeedY;
pipeX -= 5;
if pipeX < -pipeWidth {
pipeX = screenWidth;
pipeHeight = Raylib.GetRandomValue(100, screenHeight - pipeGap - 100);
score++;
}
if (birdY - birdRadius < 0) || (birdY + birdRadius > screenHeight) ||
(birdX + birdRadius > pipeX && birdX - birdRadius < pipeX + pipeWidth) &&
(birdY - birdRadius < pipeHeight || birdY + birdRadius > pipeHeight + pipeGap) {
gameOver = true;
}
} else {
if Raylib.IsKeyPressed(KeyboardKey.R) {
// Reset game
birdY = screenHeight / 2;
birdSpeedY = 0;
pipeX = screenWidth;
pipeHeight = Raylib.GetRandomValue(100, screenHeight - pipeGap - 100);
gameOver = false;
score = 0;
}
}
// Draw
Raylib.BeginDrawing();
Raylib.ClearBackground(Color.SkyBlue);
// Draw bird
Raylib.DrawCircle(birdX, birdY, birdRadius, Color.Yellow);
// Draw pipes
Raylib.DrawRectangle(pipeX, 0, pipeWidth, pipeHeight, Color.Green);
Raylib.DrawRectangle(pipeX, pipeHeight + pipeGap, pipeWidth, screenHeight - pipeHeight - pipeGap, Color.Green);
// Draw score
Raylib.DrawText("Score: " + score.ToString(), 10, 10, 20, Color.Black);
if gameOver {
Raylib.DrawText("GAME OVER", screenWidth / 2 - 100, screenHeight / 2 - 50, 50, Color.Black);
Raylib.DrawText("Press R to Restart", screenWidth / 2 - 120, screenHeight / 2 + 10, 20, Color.Black);
}
Raylib.EndDrawing();
}
Raylib.CloseWindow();
}
Variable Casting:
In ARISL, casting is done using the syntax (~type)<~variable
. For example:
birdY += (~int)<~birdSpeedY;
This line casts birdSpeedY
to an int
before adding it to birdY
.
Generic Types/Typed functions
You can use ~int,~int@
to specify a type for a function. To create c# style generic function you can do the same ~T1,T2@
public static ~void func ~T@(ref ~T lhs, ref ~T rhs)
See how we specified that it was a void function? That's only there to ease the anxiety some of you Csharp veterans are probably feeling right about now. You dont need to specify that, but if it makes you feel better I understand.
Complete Flappy Bird Game: Here's the complete code for our Flappy Bird game in ARISL:
dotnet(Raylib-cs);
using Raylib_cs;
func main() {
~int screenWidth = 800;
~int screenHeight = 450;
Raylib.InitWindow(screenWidth, screenHeight, "Flappy Bird");
Raylib.SetTargetFPS(60);
// Game variables
birdX = screenWidth / 4;
birdY = screenHeight / 2;
birdRadius = 20;
~double birdSpeedY = 0;
~double gravity = 0.5;
jumpStrength = -10;
pipeWidth = 80;
pipeGap = 200;
pipeX = screenWidth;
pipeHeight = Raylib.GetRandomValue(100, screenHeight - pipeGap - 100);
gameOver = false;
score = 0;
while !Raylib.WindowShouldClose() {
// Update
if !gameOver {
if Raylib.IsKeyPressed(KeyboardKey.Space) {
birdSpeedY = jumpStrength;
}
birdSpeedY += gravity;
birdY += (~int)<~birdSpeedY;
pipeX -= 5;
if pipeX < -pipeWidth {
pipeX = screenWidth;
pipeHeight = Raylib.GetRandomValue(100, screenHeight - pipeGap - 100);
score++;
}
if (birdY - birdRadius < 0) || (birdY + birdRadius > screenHeight) ||
(birdX + birdRadius > pipeX && birdX - birdRadius < pipeX + pipeWidth) &&
(birdY - birdRadius < pipeHeight || birdY + birdRadius > pipeHeight + pipeGap) {
gameOver = true;
}
} else {
if Raylib.IsKeyPressed(KeyboardKey.R) {
// Reset game
birdY = screenHeight / 2;
birdSpeedY = 0;
pipeX = screenWidth;
pipeHeight = Raylib.GetRandomValue(100, screenHeight - pipeGap - 100);
gameOver = false;
score = 0;
}
}
// Draw
Raylib.BeginDrawing();
Raylib.ClearBackground(Color.SkyBlue);
// Draw bird
Raylib.DrawCircle(birdX, birdY, birdRadius, Color.Yellow);
// Draw pipes
Raylib.DrawRectangle(pipeX, 0, pipeWidth, pipeHeight, Color.Green);
Raylib.DrawRectangle(pipeX, pipeHeight + pipeGap, pipeWidth, screenHeight - pipeHeight - pipeGap, Color.Green);
// Draw score
Raylib.DrawText("Score: " + score.ToString(), 10, 10, 20, Color.Black);
if gameOver {
Raylib.DrawText("GAME OVER", screenWidth / 2 - 100, screenHeight / 2 - 50, 50, Color.Black);
Raylib.DrawText("Press R to Restart", screenWidth / 2 - 120, screenHeight / 2 + 10, 20, Color.Black);
}
Raylib.EndDrawing();
}
Raylib.CloseWindow();
}
ARISL is still very much in its infancy. While it already offers a range of features and functionality, there are many areas where the language can be expanded and improved.
One of the most exciting developments is the upcoming ARISL language server, which will provide C# style IntelliSense in Visual Studio Code. This will greatly enhance the coding experience by offering auto-completion, method signatures, and other helpful features. The first version (0.0.1) will be uploaded soon, making it easier for developers to write and debug ARISL code.
We are currently working on a system that should be able to build projects into an executable with very minimal effort. This is only possible at the moment on windows.
There is already a rudimentary import system in the language that allows you to seperate your projects into several smaller .ari files, however its kinda hacky and will be replaced with a better solution. You can currently import a file that is in the same folder as an active .ari script by including import(filename.ari) at the top of the active script. It should also work if you do import(filepath.ari).
The code works and the speed is rather remarkable when compared to my eariler attempts at ARISL, however I'd like to improve certain aspects of performance such as the time it takes to transpile code into c#. Additionaly, I'd like to improve the readability of our transpiling code and hopefully reduce the length.
Creating a new programming language is a challenging but incredibly rewarding endeavor. If you're passionate about programming and have ideas for a language, I encourage you to try building it. Here are a few tips to get you started:
Start Small: Begin with a simple language specification and gradually add features. It's easier to manage a small, functional language and expand it over time than to start with an overly complex design.
Use Existing Tools: Tools like ANTLR (Another Tool for Language Recognition) can simplify the process of creating parsers and interpreters. ARISL started with the intention of transcompiling into Lua code, but thanks to ANTLR, it was easy to pivot to transcompiling into C# when needed.
Seek Feedback: Share your language with others and seek feedback. This can help you identify areas for improvement and new features that you might not have considered.
Stay Persistent: There will be challenges and setbacks, but persistence is key. When things get complex, take a step back, analyze the problem, and keep pushing forward.
ARISL is at the beginning of its journey, and with your help and feedback, it has the potential to grow into a powerful and versatile language. Whether you're using ARISL or thinking about creating your own language, the key is to start, experiment, and enjoy the process.
Happy coding!