Vadik's scratch programming language compiler

Purpose

Some scratch projects are technically possible, but require doing something that no-one would ever do by hand. This compiler aims to automate those hard parts and make creation of such projects actually viable. Also note that this compiler is not designed to make a fully working scratch projects. It is only intended to simplify creation of computational parts of the projects (parts that do math with data in variables and lists), while still requiring user to make the rest of the project in the scratch editor in the normal way.

General overview

Before usage, compiler needs to be provided with what to compile. It can be done in one of 2 ways:

After that, the "Compile" button should be clicked. The compiler will recursively go through all subdirectories and find files that end with .vspl which it will later try to compile. It will also look for files called exportList.txt and Compiled.sb3.

exportList.txt is used to describe which functions should be placed to which sprites. If specified function relies on some other un-specified functions, those will also be included automatically.

Compiled.sb3 if exists, would automatically be overwritten with the compilation output without prompting user to save the file.

If you still aren't sure how to do it, you can check this example.

Language syntax

In most cases amount of spaces and placement of newlines doesn't matter.

// can be used to mark the rest of the line as comment.
/* can be used to start a multiline comment.
*/ can be used to end a multiline comment.

Each file starts in outer descriptions mode.

Outer descriptions

In this mode the following structures can be used:

variable YourVariableNameHere; is used to create a variable. Multiple variables can be created using a single keyword by using commas:variable Variable1, Variable2, Variable3;

list YourListNameHere; is used to create a list. Multiple lists can be created using a single keyword by using commas:list List1, List2, List3;

listoffsets YourListNameHere YourListOffsetHere; is used to define a step size for the list.

listoffsets YourListNameHere YourListOffsetHere {YourOffset1NameHere: YourOffset1ValueHere, YourOffset2NameHere: YourOffset2ValueHere}; is used to both define a step size for the list and named offsets.

const YourVariableNameHere = YourValueHere; is used to define a constant. In this programming language constants syntaxically look like variables, but when compiled, their values get directly inserted in places where they are used. Multiple constants can be created using a single keyword by using commas. Also note that values of constants defined here can only consist of 1 token. Setting it's value to something like 12+34 is invalid here, since that is 3 tokens.

namespace YourNamespaceNameHere {} is used to define namespaces. It's content should be defined within figure brackets. This can be used to neatly keep similarly named things isolated from each other. To access something from the namespace use dot syntax: NameOfNamespaceTheThingIsIn.TheNameOfTheThing. When things from some namespace try to refference things from the same namespace using dot syntax is optional, you can just use names directly. Also note that if something isn't found in the current namespace, the compiler will not recursively try searching it in more and more outer namespaces, it will just jump straigt to the global namespace and try to find it there. Also, if the same namespace is defined multiple times in different places, the content will be merged.

namespace YourNamespaceNameHere "ScratchPrefixHere" {} is the same as before, but can be used to also set visual prefix to everything inside of it. It can be seen in the compiled scratch project.

function FunctionNameHere() {} is used to define functions - the things that actually contain code. Inside of the round brackets the list of arguments separated by commas is defined. In figure brackets the actual code is defined.

inline function FunctionNameHere() {} is used to define inline functions. They can also have arguments. They are like normal functions, but they get pasted into the place they are called from. Here they can be used for either stack blocks or reporter blocks.

Here is an example code:

Which compiles into:

Inner code

Inner code refers to anything within function definition and there is a lot to cover.

= can be used to set variables or list elements to some value. What needs to be changed is on the left, where to take value from is on the right. It is also possible to set the contents of entire list using: list = [value1, value2, value3]

+=, -=, *=, /= can be used to modify values on the left in specified way.

+, -, *, /, % can be used to perform mathematical operations. First value should be on the left and the second on the right. % is mod operator. If left value of - is missing, 0 will automatically be inserted. *, /, % have higher priority than +, -

> comparathon if value on the left is greater than on the right.

< comparathon if value on the left is smaller than on the right.

>= comparathon if value on the left is greater or equal to the one on the right.

<= comparathon if value on the left is smaller or equal to the one on the right.

== comparathon if values on the left and right are equal.

&& logical and for values on the left and right.

|| logical or for values on the left and right.

! logical not for value on the right

() taking things into brackets can be used for changing order of operations.

functionHere(argumentsHere) can be used to call a function.

someNamespace.property can be use to access properties of namespaces.

something trying to read a value of the thing. This will first try to find it in local scope of the current block within the function, if not found, try to find in the namespace the function is in and then, if still not found, search in the global namespace. Names of things can't start with numbers.

"something", 'something' string of text.

1234.567 numbers are written directly.

list[item] square brackets can be used to access specific element of the list.

list[item]#offsetName this could be hard to explain, but code example may help. This:

is just a cleaner way of writing this:

list[item]#offsetAmount same, but instead of using names, it has a numeric offset directly. At first glance it may seem useless, but in combination with some other features of this programming language it becomes really usefull.

Local things

There exists 4 types of local things defined by keywords: const, let, lst, sal (and a 5th type which is function arguments).

All 4 support defining multiple at once by separating with commas.

const name=value; constants. Works the same as outside functions, except here they can handle multi-token expressions. Doing something like this is totally valid:

And it will behave as if it was:

This is also where named # and numeric # become really useful:

That code will compile into:

let name; or let name=value; local variables. When compiled, all of them get assigned name that starts with letter s followed by a number. For each function numbers start from 0 and for every local variable go up by 1. During linking stage, variable numbers get shifted up to avoid conflicts.

lst name;,lst name=[]; or lst name=[commaSeparatedValues]; local lists. Work exactly like local variables. By default they keep the old garbage data from somewhere else. Using =[] clears them. Optionally things can be listed inside [], which will sequentially get added after clearing.

sal ListNameHere SalName; is for statically allocated list positions. Interesting fact is that local variables can be workarounded through sal. In fact in code let and lst are just extensions built on top of sal system.

Let's assume you store some structured data in list. Each element defined by you takes up a fixed amount of actual list elements. Some of your elements get created and deleted dynamically while program is running and that is simle to implement. However some of those your elements need to be temporarily used during computations. Dynamically allocating them, doing computation and deleting them seems too computationally expensive. A much better solution would be to just reserve some parts of list and just once hardcode what part of code can use what space in list reserved for it without conflicting with other parts of the code. However doing it manually is very tedious, and in large project is unfeasable. Sal does that automatically.

The most common example of how it can be used is for vector, matrix and quaternion operations, where you would probably want to have "local variables" to temporarily store those during calculations and discard them after computation is done. So on example:

In this example list Vector3D has a step of 3. This means that sal will set crossBC = 0, crossEF = 3, sumA = 6, sumD = 9. If function "main" is called from somewhere else, and that somewhere else for example also has 2 sal of Vector3D, then sal here during linking process will get shifted into crossBC = 6, crossEF = 9, sumA = 12, sumD = 15. Once compiled it will just insert those numbers in places where sal "variable" is refferenced.

Branching

By default those will only take 1 operation, but {} can be used for multiple operations. Like in most other languages

if(conditionHere) actionHere

if(conditionHere) action1Here else action2Here

forever actionHere

while(conditionHere) actionHere

until(conditionHere) actionHere

repeat(amountOfTimes) actionHere

for(variable; amountOfTimes) actionHere uses scratch's hidden for each loop. In variable you can either reference one of the existing variables (using dot syntax if it is in other namespace), or you can start variable by let and then it will be created and only accesible within the for loop. Outside that for loop, compiler might decided to reuse the same actual scratch variable for something else even within the same function.

In function scoping

{ } creates a new scope for anything inside. const, let, lst, sal defined inside do not exist on the outside.

Automatic optimization

+ and - are reordered and constant values are preprocessed. If you write a+6-7+11, the compiled sb3 will only have a+10. If you write -a+b the compiler will reorder it to b-a.

Built-in functions and properties

return gets compiled into [stop [this script]]. Unlike in other languages, doesn't actually return any value.

YourListHere.length length of the list.

YourListHere.reserved amount of list elements reserved by sal variables.

YourListHere.push(value) adds value to the list. Can have multiple arguments.

YourListHere.delete(index) removes item from the list.

YourListHere.has(value) checks if list contains a value.

YourListHere.indexOf(value) finds an index of an element with a value.

YourListHere.insert(index, value) inserts a value to the place where index is.

YourListHere.clear() removes everything from the list.

Math.sin(value), Math.cos(value), Math.tan(value), Math.asin(value), Math.acos(value), Math.atan(value), Math.round(value), Math.floor(value), Math.ceil(value), Math.abs(value), Math.sqrt(value)

Pen.clear(), Pen.down(), Pen.up(), Pen.setSize(size), Pen.moveTo(x, y)

Naming function "whenFlagClicked" turns it into when flag clicked block when compiled instead of custom block definition.

exportList.txt syntax

Each entry is written on a new line. Each line starts with the sprite name followed by a single space, followed by the dot separated path to the function. If line has more or less than 1 space, it will be skipped. Lines that start with # are also skipped. If line is #stop, lines after it are not processed. If path points to * inside of some namespace, it will recursively find and mark all functions inside that namespace.



Go back to compiler
Vadik1 on Scratch | Other tools | Source code