This is the repo for the main compiler. The Libraries are located here: https://github.com/TheblueMan003/StarLightLibraries/
Here are some notion used in this ReadMe:
<value>
needed value
[value]
optional value
value*
repeated value (usually splitted by a commat)
Use the following command in a cmd prompt or a terminal to open the compiler:
java -jar <file>
Once the compiler is open you will have access to the following command.
help
: Show the list of available commandnew
: Create a new project in the current directorybuild <path_to/build.slconf>
: Compile & Generate the packs. By default,build java
build a Java Datapack &build bedrock
build a bedrock Behaviorpackinstall <library> [version]
: Install a library into the local project. If version is not specified, the latest version will be installed. Note that standard libraries are downloaded automatically when needed.update <library>
: Update library to latest version.
An alternative is to use the following command:
java -jar <jarfile> build <path_to/build.slconf>
Variables can be declared with
int a
int b, c
Variables can be declared and assigned
float a = 10
float b, c = 10
float d, e = (10, 11)
Variables can be assigned
a = 10
a, b = 10
d, e = (10, 11)
Scoreboard can be created by adding the scoreboard
modifier befor the type when creating a variable.
scoreboard int myScore
Scoreboard variable can be either referred with their name or with a selector followed by a dot and their name.
with(@a){
myScore = 0
}
@p.myScore = 1
Access modifiers like private
, protected
& public
can be added before the type when creating a variable.
private int a
Private ban the access to the variable from from another context.
Protected allow the variable to be accessed anywhere.
Public allow the variable to be accessed anywhere & are exported to the interface for other datapack.
By default variable start with protected
.
Variable that are mark as lazy
will have there value computed during the compilation.
lazy int a = 5
a += 10
int b = a
Will result into the following code:
int b = 15
If value of the variable cannot be computed during the compilation the global expression will be keep:
int f
lazy int a = 5 * f
int b = a
Will result into the following code:
int f
int b = 5 * f
This can be useful for string and json operation at compile time.
Variable can be marked as constant with the const
keyword. This will prevent reassignement of the variable.
const int a = 0
Note that this only prevent the variable from being reassign within its block, meaning that it can be reassign when a function is called multiple time.
If you don't want to use type for your variable you can use the val
and var
keyword.
val a = 4
var b = 5
val
is equivalent to const <type>
. The type of the variable is infered by the right expression. The type cannot change after that.
Attributes can be added to function to specify thing to the compiler.
[criterion="minecraft.used:minecraft.carrot_on_a_stick"] scoreboard int a
Here is a list of used attributes by the compiler:
criterion
: specify the criterion when other than dummy. (JAVA Only)nbt
: specify the nbt of the variable (for variable of type json & marked as scoreboard)type
: specify the type of the variable for nbt assigment (for variable of type json & marked as scoreboard) (by default it is infered at asignment location but double is not supported by the inferer and must be specified manually)name
: force the name of the variable (use for inter compatibility with other datapack)scoreboard
: force the scoreboard of the variable (use for inter compatibility with other datapack)versionSpecific
: Append the version to the name of the variabletag
: force the tag use by the variable (for variable of type entity)
Store Normal integer
Supported operation:
=
,+
,-
,*
,/
,%
,<<
,>>
,++
,--
,^
Note that^
is the power operator and not the xor operator.
Store Fixed point value with 32 bits (by default keep 3 digits after dot)
Supported operation:
=
,+
,-
,*
,/
,%
Store boolean value
Supported operation:
=
,+
,-
,*
,/
,%
,&
,|
,&&
,||
Store one or multiple entities
Supported operation:
=
: The variable to have the entity&
: Intersection of entities set|
: Union of entities set-
: Difference of entities set
Tuple, Store multiple value. e.i. (float, float, float) position = 0,0,0
All operators are supported as long as either the operation can be done on every single value or the right hand side is also a tuple with the same size and the operation can be perform with each corresponding entry.
Store non lazy function (e.i. void=>void)
Supported operation:
=
: The variable to have the variable of function()
: Call the contained function
The entity can be assigned a selector. This will result in only the entity selected by the selector to be inside the variable.
entity players = @a
The selector can have either the java or bedrock syntax.
Sets operations can be performs on the variables of type entity.
players += @a
players -= @p
In addition variables of type entity and selectors can be used inside if. It will return true if there is at least one entity that match the selector or is in the variable.
entity a = @p
if (a && @e[tag=hello]){
}
You can test if an entity belong to the variable with the following syntax:
entity set
if (@s in set){
}
Entity variable & Selector support the following functions:
entity.effect.<effect>()
: Give the entity the effect (duration is infinite)entity.effect.<effect>(int duration)
: Give the entity the effect for the durationentity.effect.<effect>(int duration, int amplifier)
: Give the entity the effect for the duration with the amplifierentity.effect.<effect>(int duration, int amplifier, bool showParticles)
: Give the entity the effect for the duration with the amplifier and showParticlesentity.effect.clear<effect>()
: Clear the effect from the entityentity.effect.clear()
: Clear all the effect from the entityentity.tag.add(string tag)
: Add the tag to all the entity in the variable/selectorentity.tag.remove(string tag)
: Remove the tag to all the entity in the variable/selectorentity.count()
: Return the number of entity in the variable/selectorentity.kill()
: Kill all the entity in the variable/selectorentity.despawn()
: Despawn all the entity in the variable/selectorentity.swap(entity other)
: Swap the entity in the variable/selector with the entity in the other variable/selectorentity.teleport(entity other)
: Teleport the entity in the variable/selector to the entity in the other variable/selectorentity.teleport(mcposition pos)
: Teleport the entity in the variable/selector to the positionentity.teleport(mcposition pos, float yaw, float pitch)
: Teleport the entity in the variable/selector to the position with the yaw and pitchentity.sum(int score)
: Return the sum of the score of all the entity in the variable/selectorentity.max(int score)
: Return the max of the score of all the entity in the variable/selectorentity.min(int score)
: Return the min of the score of all the entity in the variable/selectorentity.avg(int score)
: Return the avg of the score of all the entity in the variable/selectorentity.winner(int score)
: Return the entity with the max scoreentity.loser(int score)
: Return the entity with the min scoreentity.withWinner(int score, void=>void f)
: Execute the function on the entity with the max scoreentity.withLoser(int score, void=>void f)
: Execute the function on the entity with the min scoreentity.forEachOrderedAscending(int score, void=>void f)
: Execute the function on the entity in ascending orderentity.forEachOrderedDescending(int score, void=>void f)
: Execute the function on the entity in descending orderentity.forEachOrdered(int score, bool ascending, void=>void f)
: Execute the function on the entity in the specified orderentity.onNewHighScore(int score, int previous, void=>void f)
: Execute the function on the entity when the score is a new high scoreentity.onNewLowScore
: Execute the function on the entity when the score is a new low score
Store a json value. The json value can be either a json object, a json array, a json string, a json number, a json boolean. When it is marked as scoreboard, the behavior is undefined unless the "nbt" attribute is set. In case, the json will be stored as nbt on the current entity.
Supported operation:
=
: The variable to the json<:=
: Prepend the json>:=
: Append the json::=
: Merge the json-:=
: Remove the json==
: Compare the jsonin
: Check if the json is in the other json
Store a string value.
Supported operation:
=
: Asign the string+=
: Append the string==
: Compare the stringin
: Check if the string is in the other string
Supported function:
string.length()
: Return the length of the stringstring.replace(string, string)
: Return the string with the first string replace by the second stringstring.contains(string)
: Return true if the string contains the other stringstring.startsWith(string)
: Return true if the string start with the other stringstring.endsWith(string)
: Return true if the string end with the other stringstring.indefOf(string)
: Return the index of the first occurence of the stringstring.lastIndexOf(string)
: Return the index of the last occurence of the stringstring.toUpper()
: Return the string in upper casestring.toLower()
: Return the string in lower casestring.leftTrim()
: Return the string without the leading spacestring.rightTrim()
: Return the string without the trailing spacestring.trim()
: Return the string without the leading and trailing spacestring.reverse()
: Return the reversed string
The language support if
with a c like syntax:
if (a > 0){
}
else if(b == 0 && a < 0){
// b==0 and a < 0
}
else if (b == 1 || a < 0){
// b ==1 or a < 0
}
else{
}
If a case is always true if will be remove the other following cases in the output code. If a case is always false it will be removed from the output code.
The compiler allow a special expression to test condition on multiple entities at once. The following code will only say hi if value is true for all player.
if (value for all in @a){
/say hi
}
The following code will only say hi if value is true for any player.
if (value for any in @a){
/say hi
}
The following code will only say hi if value is false for all player.
if (value for none in @a){
/say hi
}
A more simplier way of having multiple if is to use a switch statement:
int a, b
switch(a){
0 -> do_stuff()
1 -> {
do_other_stuff()
}
2 if b == 1-> {
/say hi
}
3..4 -> {
/say hi
}
default -> {
/say bye
}
}
The switch statement will build a tree if the number of cases if big enough. (20 by defaut) In addition to that switch also support tupple value.
(int, int) a
switch(a){
(0,0) -> do_stuff1()
(0,1) -> do_stuff2()
(1,0) -> do_stuff2()
(1,1) -> do_stuff2()
}
In that case it will build nested trees.
You can also an auto generated switch with the following format:
int value
switch(value for t in 0..10){
t -> print(t)
}
Note: This is used to precompute operation that cannot be computed in Minecraft. Example: String concatenation with a number, Getting value from lazy json variable, etc.
The language also support the following loop: for, while, do while with a c like syntax:
for(int a=0;a < 10;a+=1){
}
while(a > 0){
}
do{
}while(a > 0)
Instructions can be executed at an entity with:
at(@a){
do_stuff()
}
Instructions can be executed at a position with:
at(~ ~1 ~){
do_stuff()
}
Instructions can also be executed in a ranged position:
at({~, ~0..10, ~}){
do_stuff()
}
Instructions can be executed as an entity with:
as(@a){
do_stuff()
}
For compatibility with BluePhoenix the following also work:
// as @a at @s if a == 0
with(@a, true, a == 0){
do_stuff()
}
// as @a if a == 0
with(@a, false, a == 0){
do_stuff()
}
with(@e){
do_stuff()
}
else{
do_stuff_if_no_entity()
}
(Java Only)
To execute commands with the attacker, controller, leasher, origin, owner, passengers, target or vehicle of an entity, you can use the following selectors:
@attacker
, @controller
, @leasher
, @origin
, @owner
, @passengers
, @target
and @vehicle
with(@attacker){
// do stuff
}
To execute on top of an height map you can use the following selectors:
@world_surface
, @motion_blocking
, @motion_blocking_no_leaves
, and @ocean_floor
at(@world_surface){
// do stuff
}
Functions can be declared with
def <modifier> <name>([<type> <argument name>]*)<block or instruction>
or with
[def] <modifier> <type> <name>([<type> <argument name>]*)<block or instruction>
Arugment can also have default value:
def hello(int a = 0){
}
Functions can be called with:
function_name(arg1, arg2)
Function can also return value:
bool test(){
return true
}
Function can be put inside variable:
int plus(int a){
return a + 1
}
int=>int fct = plus
int b = fct(5)
Lambda can be created with the syntax:
(int, int)=>int fct = (a,b)=> { return a + b }
Note that Lambda refere to the variable outside of it by reference & not by value. Meaning that if you change the value of a variable outside of the lambda it will also change inside the lambda and vis-versa. (These are not closure.)
You can call a variable of type function with the following syntax:
def fct(void=>void arg){
/say befor
arg()
/say after
}
fct(){
/say hi
}
With the latter syntax the lambda is always the rightmost argument of the function; not including optional argument.
Function can also belong to a tag by adding an name prefixed by @
:
def @tagExample test(){
}
If the tag doesn't exist before it will be created. Tags allow you to call all the function that belong to it by calling the tag name:
@tagExample()
Note that tags are global across the code context.
Attributes can be added to function to specify thing to the compiler.
def [tag.order=10] @tick test(){
}
Here is a list of used attributes by the compiler:
tag.order
: (float or int) Order for which it will run when call by a tag. Lower number are call first.compile.order
: (float or int) Order for the function is compile. Lower number are compiled first.inline
: (bool) Make that lazy function call is not on a sub context. It allow to make the inner state of the function visible outside the call.
Library can be imported with the following syntax:
import math
int a = math.abs(5)
Package can also be given alias uppon importation:
import math as mt
int a = mt.abs(5)
Object can be be directly imported from a library:
from math.vector3 import Vector3
Vector3 vec
Imported object can also be given alias uppon importation:
from math.vector3 import Vector3 as v3
v3 vec
You can also import everything from a package:
from math.vector3 import _
Vector3 vec
Jsonfile can be added with the following construct:
jsonfile advancements.name{
<json content>
}
Jsonfile can also take a lazy variable containing a json object:
lazy json jsonObj = {
"a": 0
}
jsonfile advancements.name jsonObj
Jsonfile can also take a mix of json object and json variable:
lazy int a = 0
jsonfile advancements.name{
"a": a
}
Attributes can be added to jsonfile to specify thing to the compiler.
[java_rp=true] jsonfile models.block.stone{
}
Here is a list of used attributes by the compiler:
java_rp
: (bool) Add the file to the Java Resources Packbedrock_rp
: (bool) Add the file to the Bedrock Resources Pack
You can "pause" the execute of a function with sleep. For Instance, the following code will print "hello" and wait 20 ticks before printing "world":
def example(){
print("hello")
sleep 20
print("world")
}
If you call the above function inside another function, the calling function won't have its execution paused. To have the calling function be also pause the called function must be mark as async
and the await
keyword must be used a the call site.
For example:
def async foo(){
print("hello")
sleep 20
print("world")
}
def bar(){
print("hello")
await foo()
print("world")
}
will result as hello hello world world
Predicate can be defined like function but with a json body.
predicate example(int count, string itemID){
"condition": "minecraft:entity_properties",
"entity": "this",
"predicate": {
"equipment": {
"mainhand": {
"items": [
itemID
]
"count": count
}
}
}
}
They can only be call inside conditional statements like if, while, etc
Complexe type can be assign to a name with the following syntax:
typedef int=>int fct
fct foo = (a)=>{ return a + 1 }
Data structure can be define with the following syntax:
struct vector3{
int x
int y
int z
def __add__(vector other){
x += other.x
y += other.y
z += other.z
}
}
They can then be used as any other type:
vector3 a
vector3 b
a.x = 0
a += b
Here is the list of possible operator overloading:
__set__
:=
__add__
:+=
__sub__
:-=
__mul__
:*=
__div__
:/=
__mod__
:%=
__and__
:&=
__or__
:|=
__lt__
:<
__le__
:<=
__eq__
:==
__ne__
:!=
__ge__
:>=
__gt__
:>
__init__
/this
: Constructor
It is also possible to cast a json to a struct provided that the name inside the json are the same as inside the struct:
vector3 a = {x: 0, y: 1, z: 2}
Structs can also be extended. They will recieve all the methode & variable from the parent struct.
struct vector4 extends vector3{
int a
}
Struct can also accept type parameters with the following syntax:
struct Struct<T>{
T vari
def this(T value){
vari = value
}
}
Struct<int> example = new Struct<int>(0)
Classes can be define with the following syntax:
class cow{
int health
}
Class also support operator overloading with the same syntax as structs.
Class can also accept type parameters with the following syntax:
class Class<T>{
T vari
def this(T value){
vari = value
}
}
Class<int> example = new Class<int>(0)
Class use an entity, to store data. By default it use a marker entity but you can change it with the following syntax:
class Cow with minecraft:cow for mcjava with minecraft:pig for mcbedrock{
def this(){
}
}
With class you can downcast a instance to one of its parent parent.
class A{
}
class B extends A{
}
A a = new B()
In this case, if a method is use it will take the one from A.
To have proper method override you need to use abstract
,virtual
,overriding
keyword like in c#.
class A{
public abstract foo()
public virtual bar(){
/say I'm a
}
}
class B extends A{
public override foo(){
/say hi
}
public override bar(){
/say I'm b
super.bar()
}
}
A a = new B()
a.foo() // call foo from B
a.bar() // call bar from B
Templates are ways of having static class with inherritance.
template example{
def test(){
}
}
template example2 extends example{
}
Template can be "Apply" with the following syntax:
example2 foo{
def bar(){
test()
}
}
Additionnaly, you can access the other of the template with the super keyword:
def foo(){
...
}
template example{
def foo(){
super.foo()
}
}
The super keyword do not need to be use if the function is not shadowed.
Type can be extended with the following syntax:
extension int{
int plusOne(int value){
return 1
}
}
The type will then have the new method:
int a = 0
int b = a.plusOne()
Note that the first argument of the method is always the type itself.
It is possible to add extension without importing the library with the following syntax:
extension string{
def standard.string.length(string s) from standard.string as length
}
This will add the method length to the string type. The method will be call length and will be from the standard.string library. The library will only be imported if the method is used.
Template can also accept type parameters with the following syntax:
template example<T, U>{
T vari = U
def __init__(T value){
vari = value
}
}
The value can be either a type or a value and can be use in the template body. When Instantiating the template, the type parameter can be given with the following syntax:
example<int, 0> instance
Macro functions only work for Minecraft Java and follow the same logic as in Datapack. A command with a $ inside it will be replace by the value of the variable. Note that will not be usable inside the macro function as a variable for optimization purpose.
def macro test(int a){
/say $(a)
}
test(0)
Note: You do not need to prefix command with $
like in a datapack.
With the macroConvertToLazy
Compiler setting activated the function will be converted to a lazy function in the case where the arguments provided are all value.
Lazy functions are also not exported into the output code. Instead when they are called, there content is replace at the call site.
def lazy test(int a){
int b = a
}
test(0)
Will be the same as
int b = 0
Every argument of the function will be a lazy variable. If the variable start with a $
it will be replace literally instead of being replace by it's value.
Every call for function that are not mark as inline is done in a sub context meaning that variable inside it won't be usable after the call.
Note: Lazy function can be recursive, but you must be careful to not create infinite loop, or the compiler will crash.
Function can also support type parameters with the following syntax:
T function<T>(T a){
return a
}
int a = function(0)
where T is the type parameters.
When a if statement is compiled, the expression inside it is simplified. If the result value is always true then the condition will disapear from the output code and all the else statement with it.
if (true){
//do stuff
}
else{
// crash
}
will become
//do stuff
This can be used to compile according to some compilation settings. Here is a list of meta variable that can be tested:
Compiler.isJava
: Tell if the target is MC JavaCompiler.isBedrock
: Tell if the target is MC BedrockCompiler.isDebug
: Used to add extra info in the datapack that can be used to debugCompiler.isEqualitySupported<T>
: Check if the type provided T support equalityCompiler.isComparaisonSupported<T>
: Check if the type provided T support comparaison@tagExample
: where tagExample is a function tag. Tell if there is at least one function inside the tag when the compiler reach the condition.
When a lazy variable get assign a value. The type of the value is keep inside the lazy variable. For instance:
lazy float a = 1
Will contains an int value.
It is therefor possible to write the following code:
if (a is int){
print("Is int")
}
if (a is float){
print("Is float")
}
if ("key" in dict){
print("Is dict")
}
This is computed at compile time and not runtime.
The foreach statement allow to iterate over a list of value and generate code for each of them.
foreach (i in (0, 1, 2)){
print(i)
}
Will generate:
print(0)
print(1)
print(2)
Note that forgenerate "copy paste" the code inside the loop for each value. This mean that there is no loop at runtime.
The allowed generators are:
<start>..<end> [by <step>]
: Generate a range of number#<blocktag | itemtag | entitytag>
: Generate all the block inside the tag@<functiontag>
: Generate all the function inside the tag<json array>
: Generate all the value inside the array<json object>
: Generate all the key inside the object<tuple>
: Generate all the value inside the tuple
Forgenerate is a legacy syntax that is not recommended to use. You can use foreach instead. Forgenerate can be used to generate code but work by replacing literal value inside the code. For instance:
forgenerate ($i, (0, 1, 2)){
/say $i
}
Will generate:
/say 0
/say 1
/say 2
However, it is not possible to use the following syntax:
forgenerate ($i, (0, 1, 2)){
a = $i
}
In this case the compiler will think that $i is a variable and not a literal value.
The compiler offer some function that can be used to generate code. They are:
Compiler.insert(<$name>, <value>){<code>}
: Insert the value inside the code. The value can be used with the syntax$name
:
Compiler.insert($i, i){
/say $i
}
Mutliple value can be insert at the same time:
Compiler.insert(($i, $j), (i, j)){
/say $i $j
}
Compiler.readJson(<path>)
: Read a json file and return it as a lazy json objectCompiler.random()
: Return a random number (At compile time)Compiler.variableExists(<name>)
: Return true if the variable existCompiler.getTemplateName()
: Return the name of the current templateCompiler.sqrt(<value>)
: Return the square root of the valueCompiler.pow(<value>, <power>)
: Return the value to the power of the powerCompiler.powInt(<value1>, <value2>)
: Return the value1 to the power of the value2Compiler.hash(<value1>)
: Return the hash of the valueCompiler.getObjective(<variable>)
: Return the objective of the variableCompiler.getSelector(<variable>)
: Return the selector of the variableCompiler.getContextName()
: Return the name of the current contextCompiler.getVariableTag()
: Return the tag of the current variable (for variable of type entity)Compiler.toNBT(<value>)
: Return the nbt of the json valueCompiler.getProjectVersionType()
: Return the version type of the project alpha, beta, releaseCompiler.getProjectVersionMajor()
: Return the major version of the projectCompiler.getProjectVersionMinor()
: Return the minor version of the projectCompiler.getProjectFullName()
: Return the full name of the projectCompiler.getProjectName()
: Return the name of the projectCompiler.print()
: Print a message in the consoleCompiler.replace(<src>, <from>, <to>)
: Replace all the occurence of from by to in src stringCompiler.toRadians(<value>)
: Convert the value to radiansCompiler.toDegrees(<value>)
: Convert the value to degreesCompiler.makeUnique(<selector>)
: Return a selector that is uniqueCompiler.interpreterException(<message>)
: Throw an exception with the message in the interpreterCompiler.cmdstore(<variable>){<code>}
: Store the result of the code inside the variable