(This documentation is heavily inspired by documentation of V lang)
SlimScript is a static typed scripting language. Since I did not have enough mental power and knowledge when developing this language, the language is not compiled to byte code or machine code. It's interpreted. Line by line. So it's slow. Very slow. It was designed merely for fun. I was bored and wanted to feel cool so I made this.
It's theoretically Turing-complete. It looks a bit like Lua. It does not have such cool features, the only cool thing about it is that you have access to C# from inside the script. I didn't test it but potentially it has the ability to use any .NET language from the script.
This little cool feature of it could be used to add a basic app of yours the ability to be modded. A basic language for basic uses.
Also SlimScript is designed to be a language that is close to speaking. Meaning when you write code, the code also makes sense linguistically. We'll see this later in this documentation.
Just go to the Releases page and grab the installer of the last version,
or;
You can build the project by yourself, copy the appinfo.json
and lib/
to %AppData%/SlimScript/
, add the executable to your path and be on your way.
NOTE:
I made this project 2-3 years ago, when I was just getting comfortable with programming. And my machine was Windows at that time. So I've never used SlimScript on a Linux machine. And so there is no support for Linux (sadly)<I'm sorry>.
ANOTHER NOTE:
I previously had a repo, which I had created when I started this project, but due to some complications, I had to delete it and recreate the repo, resulting in this blank commit history. A part of the changelog is still available tho.
!!! ANNOUNCEMENT !!! As of 25.08.2024, I'm happy to announce that I've tested SlimScript on a Linux machine (with Debian 12). There was some problems at first but After making a couple little changes (mostly because of path delimiters), it runs smoothly now.
- SlimScript Documentation
- Introduction
- Installing SlimScript
- Table of Contents
- Hello World
- Passing Parameters
- Comments
- Functions
- Symbol Visibility
- Variables
- Data Types
- Modules
- Statements and Loops
- Treating Functions as Variables
- CLR Type and Using .NET
- Memory Management
- Throwing Errors
- Builtin Functions
- Builtin Function-ish Keywords
- Appendices
- Using CLI
func main args argc begin
write "Hello World"
end
Save this to a file with extension .sscript
. Then do a SlimScript <your_file>.sscript
Assuming you have SlimScript in your environment variables. If you do not then write the path to SlimScript manually.
Voila! That was your first SlimScript script. You can compresspile (compress + compile) your script using SlimScript
You can see the modes of SlimScript by passing the flag
-h
or--help
as an argument.SlimScript -h
orSlimScript --help
The main function is optional. You can write the program above with just write "Hello World"
. We'll discuss this with more detail later.
You probably recognized the parameters args
and argc
. These are exactly what you think they are. Arguments
and argument count
respectively. This documentation will show you how to use arrays and variables later but for now you should know that args
is an array. It's null by default if nothing is passed and argc
is zero.
You can pass parameters by using %p
in command line.
SlimScript main.sscript %p Some Args Here And Here
Now you can use this in your main function, or you can use os.args
and os.argc
in system
module.
-- This is a single line comment
-- Unfortunately there are no multiline comments in SlimScript
func writeSmth smth begin
write smth
end
func writeSmth' smArr begin
append 5 to smArr
write smArr
end
There is no need to specify types. Technically you can pass parameters with wrong types but please don't do that. I didn't implement type checking in functions. I mean I could've done it but I did not. I can't change it now.
Functions cannot be overloaded but you can include non-lethal symbols in your function name. For example the symbol !
and '
are not special symbols in SlimScript. So when you want to overload the function add
you can simply name it add'
or add!
or even !add'
!
When you call a function, that actually means that you are saying "Do this function" to the computer. So in SlimScript we call functions by do <function_name>
.
do writeSmth "Smth" -- Smth
It's as simple as that!
Just as usual, we use return
keyword. You can discard the return values, or define a variable with them.
func add x y begin
return + x y
end
define var as do add 5 4 -- var = 9
do add 9 6 -- returns 15, but its discarded
Functions canNOT be used before their declaration. Because I was so lazy to implement it. Functions are treated like variables is SlimScript. Meaning they can only be used after their declaration. But you can use undefined functions inside a function. Because SlimScript is completely interpreted meaning unless you call the function, program does not know what is inside of it so it won't care if you use something that don't exist.
Operators are also functions. That's why we use the stupid syntax <operator> <operand> <operand>
. The list of operators and standard functions will be shown later. I just wanted to clear up your thoughts such as "Why the hell we use operators this way?"
There is no such thing as access modifiers
in SlimScript. Everything is public as long as you know their name!
define name as "Duke"
define age as 45
define arr as [5,4, "Hello", "Another", "Array"]
define nullVar as null
write name
write age
write arr
write null
Variables are defined with the keyword define
(How surprising). Unlike some languages, you can't declare a variable and initialize it later. You have to define and initialize it together. Meaning every variable has to have a initial value.
Preferred naming style is camelCase
. It just looks right.
Assignment is done with the keyword set
.
set name to "AnotherName"
set arr to 5
set age to ["An", "Array"]
set nullVar to "NonNull"
Since SlimScript is static, you absolutely can't change the type of the variable. It'll cause a DisordantTokenError
.
But you know, you can set a null variable to a non-null value. SlimScript will then decide the type of the variable. After that you can't change its type. It's the whole purpose of the type null
.
define variable as null
set variable to 5
write "variable is : " variable
set variable to "No"
Output:
variable is : 5 Cannot set variable 'variable' to 'No'. Type 'Number' does not match 'Word'. File: main_p.sso Line: 4 Expression: set variable to "No" Exit Code: 10 <DisordantTokenError>
As you can see, unless the type is null
, you can't set the value of a variable to a value with another type.
define someVar as "Some Variable"
func someFunc begin
define someVar as ["Completely", "Different", "Value"]
return someVar
end
write "Out someVar : " someVar
write "In someVar : " do someFunc
The output of the code above is:
Out someVar : Some Variable
In someVar : [ Completely, Different, Value ]
No need of explanation I suppose.
number (double): Double for all
bool: classic true or false
word (string): "No chars. Only words"
array: ["It", 5, variable, "can hold everything", evenFunctions]
clr: Too soon to explain this
null:
function: Yes! Function is a type!
That's all. SlimScript has only seven data types. Well null
is actually not a type. So you can even say six types.
define word as "Word"
write index 0 of word -- 'W'
define newLn as "`n" -- like PowerShell
Sadly no \u2605
's in words. I was too lazy to implement it.
Words are mutable. You can do:
define str as "Helo"
set index 2 of str to "a" -- str : "Halo"
Only double quotes can denote words. So its safe to use single quotes in a word. You can also use `q
(escape character for double quotes) in words.
Don't expect to get 195
when you do a tonumber "0xc3"
. It's not implemented. Same for octal and binary conversions.
define str as "Some"
define str2 as " Str"
define str3 as + str str2 -- "Some Str"
define str4 as "St"
set str4 to + str4 "r" -- "Str"
No. You can't concatenate a word and a number. But you can do a:
define num as 5
define str as + "Num : " tostring num -- "Num : 5"
The standard string library of SlimScript includes some more functions for string. But its not our goal to learn the libraries here. So that's all for words now.
define num as 185
No doubles, no floats, no i32, i64, byte or anything. One number type to rule them all!
An array in SlimScript is dynamic. So it can hold any type of value EXCEPT another array. I have plans to add that feature too but for now, it can't hold another array.
By saying it can't hold another array, I mean in the definition. you cant give [5, [3, 2]]
as initial value to an array but you can give [5, otherArr]
.
define arr as [5, 4, "Value"]
write arr -- [ 5, 4, Value ]
write index 0 of arr -- 5
set index 0 of arr to 0
write arr -- [ 0, 4, Value ]
We use square brackets when defining arrays. An element in an array can be accessed with index <idx> of <arr>
operation.
You can append elements with append
operator.
define nums as [1, 2]
append 3 to nums
write nums -- [ 1, 2, 3 ]
However, since arrays can't hold another arrays, appending arrays is not possible and will result a grammar error.
define nums as [1, 2]
append [5,2] to nums
Error: Arrays cannot be appended to anything.
Exit Code: 8 < GrammarError>
But just as before you can do append otherArr to nums
.
You can also use identifiers while defining arrays.
define num as 5
define arr as [num, 4]
write arr -- [ 5, 4 ]
Be aware that when you use num
in arr
, the value of num
is copied to arr
. So when you do:
set index 0 of arr to 0
write arr
write num
The output will be:
[ 0, 4 ]
5
There is an array library in SlimScript standard library. I plan to create separate docs for libraries so for now, this information about arrays is enough.
Modules are script files containing scripts that meant to be reused. Every module is a file, but every file is not a module. But what does that exactly mean? First let's see how we can create modules.
Let's assume we need a module named operations
. We first create a file named operations.sscript
.
-- operations.sscript
func libFunc begin
write "My First Module!"
end
You just have to include it in another script file. Then voila! That's a module for you.
Now technically you can import this to another script using @include operations
. But what happens if we then include another module that also includes operations
? That'd cause a circular include and our script will not work. To solve this problem we should use something like header guards
in C.
We simply put @module <module_name>
at the beginning of our script.
-- operations.sscript
@module operations
func libFunc begin
write "My First Module!"
end
Be aware that we use the file name when adding our header guard
. That's not a must, but I recommend using the file names for consistency.
Now in another file, we simply do
-- anotherFile.sscript
@include operations
do libFunc -- My First Module!
Congratulations! You've just made your first module! You can also include *.csso
files in your script. *.csso
files will be explained later in this documentation. For now just know that you can include them.
You can also use define/undef
, if/elif/else/endif
for guarding your module.
@if not operations
@define operations
func libFunc begin
write "My First Module!"
end
@endif
This might come in handy in an extreme situation. Although I've never seen a situation like that. It's best to use module
. There are some technical reasons for it too.
When you use module
, the pre-processor skips the file right away if you already have included it. But when you use the "crooked" way, pre-processor keeps going until the end of the file. Pre-processor does not know if its a header guard or just an if statement. So in large libraries, using module
is a little faster.
Let's assume that we have a folder structure that looks like this:
- base
- lib
- thirdParty
- tpLib.sscript
- lib1.sscript
- thirdParty
- main.sscript
- file.sscript
- lib
In main.sscript
you can include those modules
@include lib.thirdParty.tpLib
@include lib.lib1
@include file
-- Do something in main file
like this.
You don't have to use '.'
when including modules. You can use '\'
and '/'
too. But that's all. Use whichever suits you the best.
Sadly, there is no such thing in SlimScript. But standard libraries are splitted to multiple files when they're too long. For example the io
library is 220~ lines long. It includes both directory and file operations. But you might not want both sometimes. So because of that, standard library structure is like this:
- lib
- io
- file.sscript
- directory.sscript
- io.sscript
- io
This way you can use
@include io
@include io.file
@include io.directory
one of these according to the situation. I recommend doing the same in your projects.
No such thing. Sorry.
Actually importing modules in SlimScript can be done in two ways. One way is our classic way which is done at pre-processing time. But that way is way too limited, choosing what to include can only be done with pre-processor directives.
But the second way is way more powerful. It's called runtime importing
. It's done with the function import
. Instead of adding the modules codes into the source of our main file, it runs through the imported module, copies its variables to main stack, then destroys the temporary source code chunk created for it.
It might come in handy in a lot of situations. I don't have a specific example in my mind right now but I have an example.
Let's say we have a folder structure that looks like this:
- base
- main.sscript
- testLib.sscript
- otherLib.sscript
-- testLib.sscript
@module testLib
func testFunc begin
write "Test Lib"
end
-- otherLib.sscript
@module otherLib
func testFunc begin
write "Other Lib"
end
-- main.sscript
define module as input "Enter Library Mode : "
import module "Lib.sscript"
do testFunc
The import function takes everything to its right, gets their string representation then imports the corresponding module if it exists. Notice that when runtime importing, file extension is written manually.
Let's run the code with input test
:
Output: Enter Library Mode : test Test Lib
Now let's try it with input other
:
Output: Enter Library Mode : other Other Lib
As you can see its quite the cool feature. I'm sure you can find a way to use it.
define limit as 20
define age as 15
if < age limit then
write "You are too young"
elif = age limit then
write "You are at the limit"
else
write "You good"
end
If statements in SlimScript look like Lua's. Everything between if
and then
is used to evaluate a boolean value.
if
cannot be used as an expression. Sadly. It can be shrunk to one line tho.
define age as -1
if < age 0 then write "How?" end
That's all our poor if
can do.
For
loops in SlimScript follow the style of C. The logic style I mean.
for i as 1 || < i 5 || 1 begin
write i
end
Output: 1 2 3 4
Now let's break it down. The structure of a for
loop looks like this:
for <loop_variable> || <condition> || <increment_value> begin
... Action ...
end
Loop variable is a number. You can create a temporary variable for the loop using <name> as <start_value>
. Or you can use an existing variable with just <name>
.
Condition is independent from loop variable. As long as it returns a boolean value, everything is fine. You can call a function or use an operator. You can just loop to infinity by leaving a true
!
Increment value is the value that will be added to loop variable at the end of each loop.
define arr as [1, 4, "Hello!", "Again"]
foreach item in arr begin
write item
end
The foraech
loop is used for going through elements of an array. The structure of a foreach
loop looks like this:
foreach <element_name> in <array> begin
... Action ...
end
The element is not read-only. Nothing in SlimScript is read-only. Just know it.
I'm planning to add another standard function range
. So that we can do define arr as range <etc.>
or foreach i in range <etc.> begin
. For now there is no such thing.
define condition as true
while condition begin
define age as tonumber input "Enter Age : "
if < age 20 then
set condition to false
end
end
While loop might be the most basic loop in existence. It literally does a thing while the condition is true. It's structure is:
while <condition> begin
... Action ...
end
It was mentioned before that functions were treated like variables in SlimScript. Meaning you can add functions to arrays, pass functions to functions and return functions!
Let's go step by step.
A function can be added to an array. This allows you to create something like an action pool. Then you can iterate over the functions and call them.
func add x y begin
return + x y
end
func sub x y begin
return - x y
end
func mul x y begin
return * x y
end
define funcPool as [add, sub, mul]
define val1 as 5
define res as 4
foreach fn in funcPool begin
set res to do fn val1 res
write "Res : " res
end
Output: Res : 9 Res : -4 Res : -20
This is a perfect scenario for a scripting language. Let's assume that we need to encrypt something. But we also want to be able to change the encryption standard. What do we do?
We'll first create a function named encrypt
. This function will take another function as an argument, which will be the encryption standard.
func encrypt data standard begin
... Perhaps Some Other Operations ...
do standard data
... Some Other Operations ...
end
-- in somewhere else
do encrypt anotherData AES
-- in another place
do encrypt yetAnotherData RSA
I mean, this is a cool feature isn't it? I believe you can find a place to use this feature too.
func returnFunc begin
func someFunc begin
write "How Cool is That!!"
end
return someFunc
end
define returnedFunc as do returnFunc
do returnedFunc
Output: How Cool is That!!
Perhaps the most significant feature in SlimScript (as if there are important fetures in it) is the ability to use .NET framework. It's quite simple actually. But also complicated.
.NET is evolving but SlimScript is not. So SlimScript might not be able to use (actually, I'm writing this documentation a year after the creation of SlimScript so it is not able to use) some features. Anyway. Let's get going.
To address to a CLR type, we use a curly braced syntax :{Namespaces::To::Class}
. The rest is same.
define settings as {SlimScript::GlobalSettings}
write typeof settings
write settings
Output: CLR : SlimScript.GlobalSettings SlimScript.GlobalSettings
To address to functions from an instance or a type, we use the symbol ->
. Unfortunately we can't use a syntax like {Namespace::Class}->Function
. We first have to assign the type to a variable.
define math as {System::Math}
define pi as 3.14159265358979
write math->Sin / pi 2
Output: 1
Constructors are treated like functions. First we assign the type to a variable. Then we call the constructor and assign it to another variable.
define sb_t as {System::Text::StringBuilder}
define sb as sb_t->new
You can pass parameters to constructors. If there are multiple constructors, SlimScript will choose the most suitable one and use it.
To address to fields, properties, etc. from an instance or type, we use the symbol :
. Again we can't use a syntax like {Namespace::Class}:Field
. We have to assign the type to a variable.
define settings as {SlimScript::GlobalSettings}
write settings:AppInfo
Output: Name: SlimScript Description: Small Embeddable Scripting Language for C# Version: 1.0.3-alpha Contributors: The2ndSlimShady - github.com/The2ndSlimShady/
Setting fields and properties is quite simple in SlimScript. We just use the classic accessing syntax, then write its new value. instance:Field <new_value>
.
define sb_t as {System::Text::StringBuilder}
define sb as sb_t->new
write "Len : " sb:Length
sb:Length 5
write "Len : " sb:Length
SlimScript uses imaginary fields called thisGet
and thisSet
while dealing with indexers. This example will be enough for you to understand:
define variable as {SlimScript::Variable}
define array_t as {System::Collections::ArrayList}
define arr1 as array_t->new [5, 6, 8]
write arr1:thisGet 1
arr1:thisSet 2 0
write arr1:thisGet 2
Output: 6 0
As you can see their structure is quite simple. instance:thisGet <index>
and instance:thisSet <index> <value>
.
That's all that SlimScript can do with CLR type. It's quite limited but you know, this was just a boredom boredom go away project so I think its enough.
SlimScript does not manage the memory for you at all. Since there is no such thing as a pointer
there is no way that you can cause a memory leak.
Still, you might want to delete some variables, define them again. It's useful, say, when you want to change the type of a variable. As you know, normally that's not possible. But if you delete a variable and then define it again, it works. Let's see the example below.
define number as 5
write typeof number
delete number
define number as "Haha no longer a number"
write typeof number
Output: Number Word
As you can see we use the function delete
(how surprising) to delete variables. You can also delete functions.
func function begin
write "Exists"
end
do function
delete function
do function
Output: Exists Variable named 'function' does not exists. File: main_p.sso Line: 4 Expression: do function Exit Code: 14 <NullReferenceError>
Well, sometimes things don't go the way they should. When that happens we just throw an error and ruin the program flow. But while doing this we also provide some information about the error too.
To do this, we use the function error
in SlimScript.
func factorial n begin
if < n 0 then
error "An error occured in function 'factorial'" "'n' can't be negative"
end
... Factorial Code ...
end
do factorial -5
Output: An error occured in function 'factorial' Data: 'n' can't be negative File: main_p.sso Line: 3 Expression: error "An error occured in function 'factorial'" "'n' can't be negative"
The second parameter error data is optional.
Function | Description |
---|---|
write | Writes the given data to standard output |
clrname | Returns the CLR name of given variable/type etc. (For example clrname "a string" is SlimScript.Word ) |
delete | Deletes the given variable from stack |
do | Executes the given function |
typeof | Returns a string containing the type name of given variable |
error | Throws an error, halting the program, with given data and message |
tobool | Converts given data to bool, if it can be converted |
tonumber | Converts the given data to number, if it can be converted |
tostring | Gets the string representation of given data |
input | Reads the standart input and returns a line from it |
import | Imports modules at runtime |
It explains itself. One little thing to add is that you can pass multiple parameters. The function will concatenate them the best way it can and print them.
The others are simple so no need of sub-heading for them. But there are some function-ish keywords that should be mentioned. Let's break them into sub-headings and dive.
Function-ish | Description |
---|---|
define | Surprisingly, its a function-ish. It defines variables. |
set | Yes. Another known face. It changes the values of variables. |
index | Returns an index. Tho it has some complicated features as well. |
append | Appends something to an array or a word. |
define <name> as <value>
It simply defines variables.
set <variable> to <new_value>
set index <idx> of <variable> to <new_value>
Guess what. It sets variables.
index <idx> of <array_or_word>
Index function-ish is a special one. Because it can be combined with other functions and function-ishes. It's usually combined with set
and append
.
With set index <idx> of <var> to <value>
you can alter elements at indexes of strings and arrays.
With append <value> to index <idx> of <var>
you can insert elements to indexes of arrays and strings.
append <value> to <var>
append <value> to index <idx> of <var>
You can append or insert elements to arrays and strings with this function-ish.
SlimScript has 23 keywords:
as
begin
end
to
then
in
of
not
both
any
define
func
set
return
if
elif
else
while
foreach
index
append
for
break
Some of these keywords are half functionish things. For example index
and append
. They were mentioned before in this documentation.
There are only 11 operators in SlimScript:
+ sum
- difference
* product
^ power
/ quotient
= equals
!= not equals
< lesser than
> greater than
>= greater or equal
<= lesser or equal
Also since operators are treated like functions, there is no precedence among them.
>>> SlimScript -h
Name: SlimScript
Description: Small Embeddable Scripting Language for .NET
Version: 1.0.5
Contributors:
The2ndSlimShady - github.com/The2ndSlimShady/
Usage:
SlimScript <file>
SlimScript <file> <flags>
SlimScript <file> <flags> %p <arguments>
SlimScript -i
SlimScript -h --help
Available Flags:
-D Run With Debug Mode. Generates lexer output (for curious ones). (SlimScript <file> -D)
-C Generates Compressed Standalone Script File That Has No
Dependencies On Any Other File. (SlimScript <file> -C)
-i Run Interactive Mode (SlimScript -i)
-h --help Show This Output. (SlimScript -h --help)
Passing Arguments:
SlimScript <file> <flags> %p <arguments> Arguments are passed to main function as an array.
SlimScript has three special flags in its CLI. The flag -D
generates lexer output, the flag -i
runs the interactive mode, -h
or --help
writes the output above to the standard output and -C
generates compressed standalone files. All the flags except -C
seems to be understandable. So let us explain it.
SlimScript does not generate preprocessed files, meaning there are no such file containing your code with included modules. But sometimes we might want to have a single file containing everything it needs to be executed (a standalone file). But that standalone file of ours might be too big because of included modules. To solve both of those problems SlimScript has a compress standalone mode
.
For example this little code below:
-- main.sscript
@include system
@include io
@include array
@include math
@include string
func main args argc begin
write args
end
What a little harmless piece of code isn't it? Wrong. In debug mode (not the debug mode with -D
flag. the debug mode of SlimScript's codebase) it generates a file with 324 lines, approximately 9KB in size. But we need to make it standalone and small. What do we do? Let's run our file with mode -C
.
>>> SlimScript main.sscript -C
Successfully created and compressed standalone script file main.csso
Exit Code: 0 <Normal>
Now we have a little file named main.csso
, which can be executed with SlimScript main.csso
. It's only 2KB's in size and it runs faster than main.sscript
. It's because when executing *.csso
files, preprocessor skip everything, since everything has been preprocessed before. Be aware that runtime importing is still made in runtime, therefore you can't create standalone files when importing at runtime.