moved scarpet docs to markdown files
This commit is contained in:
1 parent
c7603038da
commit
d93a0dbdf4
19 files changed
+3154
-2712
No files matched your search
@@ -0,0 +1,276 @@
|
||||
# Fundamental components of `scarpet` programming language (towards version 1.7).
|
||||
|
||||
Scarpet (a.k.a. Carpet Script, or Script for Carpet) is a programming language
|
||||
designed to provide the ability to write custom programs to run within Minecraft
|
||||
and interact with the world.
|
||||
|
||||
The project was initially built based on the EvalEx project, however it now diverged
|
||||
so far away from the original implementation, it would be hard to tell it without
|
||||
this mention. EvalEx is a handy expression evaluator for Java, that allows to evaluate
|
||||
simple mathematical and boolean expressions. For more information,
|
||||
see: [EvalEx GitHub repository](https://github.com/uklimaschewski/EvalEx)
|
||||
|
||||
This specification is divided into two sections: this one is agnostic to any
|
||||
Minecraft related features and could function on its own, and CarpetExpression
|
||||
for Minecraft specific routines and world manipulation functions.
|
||||
|
||||
# Synopsis
|
||||
|
||||
<pre>
|
||||
script run print('Hello World!')
|
||||
</pre>
|
||||
|
||||
or an OVERLY complex example:
|
||||
|
||||
<pre>
|
||||
/script run
|
||||
block_check(x1, y1, z1, x2, y2, z2, block_to_check) ->
|
||||
(
|
||||
l(minx, maxx) = sort(l(x1, x2));
|
||||
l(miny, maxy) = sort(l(y1, y2));
|
||||
l(minz, maxz) = sort(l(z1, z2));
|
||||
'Need to compute the size of the area of course';
|
||||
'Cause this language doesn\'t support comments in the command mode';
|
||||
xsize = maxx - minx + 1;
|
||||
ysize = maxy - miny + 1;
|
||||
zsize = maxz - minz + 1;
|
||||
total_count = 0;
|
||||
loop(xsize,
|
||||
xx = minx + _ ;
|
||||
loop(ysize,
|
||||
yy = miny + _ ;
|
||||
loop(zsize,
|
||||
zz = minz + _ ;
|
||||
if ( block(xx,yy,zz) == block_to_check,
|
||||
total_count += ceil(rand(1))
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
total_count
|
||||
);
|
||||
check_area_around_closest_player_for_block(block_to_check) ->
|
||||
(
|
||||
closest_player = player();
|
||||
l(posx, posy, posz) = query(closest_player, 'pos');
|
||||
total_count = block_check( posx-8,1,posz-8, posx+8,17,posz+8, block_to_check);
|
||||
print('There is '+total_count+' of '+block_to_check+' around you')
|
||||
)
|
||||
/script invoke check_area_around_closest_player_for_block 'diamond_ore'
|
||||
</pre>
|
||||
|
||||
or simply
|
||||
|
||||
<pre>
|
||||
/script run print('There is'+for(rect(x,9,z,8,8,8), _ == 'diamond_ore')+' diamond ore around you')
|
||||
</pre>
|
||||
|
||||
It definitely pays to check what higher level `scarpet` functions have to offer.
|
||||
|
||||
# Programs
|
||||
|
||||
You can think of an program like a mathematical expression, like `"2.4*sin(45)/(2-4)"` or `"sin(y)>0 & max(z, 3)>3"`.
|
||||
Writing a program, is like writing a `2+3`, just a bit longer.
|
||||
|
||||
## Basic language components
|
||||
|
||||
Programs consist of constants, like `2`, `3.14`, `pi`, or `'foo'`, operators like `+`, `/`, `->`, variables which you
|
||||
can define, like `foo` or special ones that will be defined for you, like `_x`, or `_` , which I specific to a each
|
||||
built in function, and functions with name, and arguments in the form of `f(a,b,c)`, where `f` is the function name,
|
||||
and `a, b, c` are the arguments which can be any other expression. And that's all the parts of the language, so all
|
||||
in all - sounds quite simple.
|
||||
|
||||
## Code flow
|
||||
|
||||
Like any other proper programming language, `scarpet` needs brackets, basically to identify where stuff begins and
|
||||
where it ends. In the languages that uses much more complicated constructs, like Java, they tend to use all sort of
|
||||
them, round ones to indicate function calls, curly to indicate section of code, square to access lists, pointy for
|
||||
generic types etc... I mean - there is no etc, cause they have exhausted all the bracket options...
|
||||
|
||||
`Scarpet` is different, since it runs everything based on functions (although its not per se a functional
|
||||
language like lisp) only needs the round brackets for everything, and it is up to the programmer to organize
|
||||
its code so its readable, as adding more brackets does not have any effect on the performance of the programs
|
||||
as they are compiled before they are executed. Look at the following example usage of `if()` function:
|
||||
|
||||
<pre>
|
||||
if(x<y+6,set(x,8+y,z,'air');plop(x,top('surface',x,z),z,'birch'),sin(query(player(),'yaw'))>0.5,plop(0,0,0,'boulder'),particle('fire',x,y,z))
|
||||
</pre>
|
||||
|
||||
Would you prefer to read
|
||||
|
||||
<pre>
|
||||
if( x<y+6,
|
||||
set(x,8+y,z,'air');
|
||||
plop(x,top('surface',x,z),z,'birch'),
|
||||
sin(query(player(),'yaw'))>0.5,
|
||||
plop(0,0,0,'boulder'),
|
||||
particle('fire',x,y,z)
|
||||
)
|
||||
</pre>
|
||||
|
||||
Or rather:
|
||||
|
||||
<pre>
|
||||
if
|
||||
(
|
||||
x<y+6,
|
||||
(
|
||||
set(x,8+y,z,'air');
|
||||
plop(x,top('surface',x,z),z,'birch')
|
||||
),
|
||||
sin(query(player(),'yaw'))>0.5,
|
||||
(
|
||||
plop(0,0,0,'boulder')
|
||||
),
|
||||
particle('fire',x,y,z)
|
||||
)
|
||||
</pre>
|
||||
|
||||
Whichever style you prefer it doesn't matter. It typically depends on the situation and the complexity of the
|
||||
subcomponents. No matter how many whitespaces and extra brackets you add - the code will evaluate to exactly the
|
||||
same expression, and will run exactly the same, so make sure your programs are nice and clean so others don't
|
||||
have problems with them
|
||||
|
||||
## Functions and scoping
|
||||
|
||||
Users can define functions in the form `fun(args....) -> expression` and they are compiled and saved for further
|
||||
execution in this but also subsequent calls of /script command. Functions can also be assigned to variables,
|
||||
passed as arguments, called with `call` function, but in most cases you would want to call them directly by
|
||||
name, in the form of `fun(args...)`. This means that once defined functions are saved with the world for
|
||||
further use. For variables, there are two types of them, global - which are shared anywhere in the code,
|
||||
and those are all which name starts with 'global_', and local variables which is everything else and those
|
||||
are only visible inside each function. This also means that all the parameters in functions are
|
||||
passed 'by value', not 'by reference'.
|
||||
|
||||
## Outer variables
|
||||
|
||||
Functions can still 'borrow' variables from the outer scope, by adding them to the function signature wrapped
|
||||
around built-in function `outer`. It adds the specified value to the function call stack so they behave exactly
|
||||
like capturing lambdas in Java, but unlike java captured variables don't need to be final. Scarpet will just
|
||||
attach their new values at the time of the function definition, even if they change later. Most value will be
|
||||
copied, but mutable values, like maps or lists, allow to keep the 'state' with the function, allowing them to
|
||||
have memory and act like objects so to speak. . Check `outer(var)` for details.
|
||||
|
||||
## Code delivery, line indicators
|
||||
|
||||
Note that this should only apply to pasting your code to execute with commandblock. Scarpet recommends placing
|
||||
your code in apps (files with `.sc` extension that can be placed inside "/scripts" folder in the world files and
|
||||
loaded as a scarpet app with command `/script load [app_name]`. Scarpet apps loaded from disk should only
|
||||
contain code, no need to start with "/script run" prefix
|
||||
|
||||
The following is the code that could be provided in a `foo.sc` app file located in world `/scripts` folder
|
||||
|
||||
<pre>
|
||||
run_program() -> (
|
||||
loop( 10,
|
||||
// looping 10 times
|
||||
// comments are allowed in scripts located in world files
|
||||
// since we can tell where that line ends
|
||||
foo = floor(rand(10));
|
||||
check_not_zero(foo);
|
||||
print(_+' - foo: '+foo);
|
||||
print(' reciprocal: '+ _/foo )
|
||||
)
|
||||
);
|
||||
check_not_zero(foo) -> (
|
||||
if (foo==0, foo = 1)
|
||||
)
|
||||
</pre>
|
||||
|
||||
Which we then call in-game with:
|
||||
|
||||
<pre>
|
||||
/script load foo
|
||||
/script in foo invoke run_program
|
||||
</pre>
|
||||
|
||||
However the following code can also be input as a command, or in a command block.
|
||||
|
||||
Since the maximum command that can be input to the chat is limited in length, you will be probably inserting your
|
||||
programs by pasting them to command blocks or reading from world files, however pasting to command blocks will
|
||||
remove some whitespaces and squish your newlines making the code not readable. If you are pasting a program that
|
||||
is perfect and will never cause an error, I salute you, but for the most part it is quite likely that your program
|
||||
might break, either at compile time, when its initially analyzed, or at execute time, when you suddenly attempt to
|
||||
divide something by zero. In these cases you would want to get a meaningful error message, but for that you would
|
||||
need to indicate for the compiler where did you put these new lines, since command block would squish them. For that,
|
||||
place at the beginning of the line to let the compiler know where are you. This makes so that `$` is the only
|
||||
character that is illegal in programs, since it will be replaced with new lines. As far as I know, `$` is not
|
||||
used anywhere inside Minecraft identifiers, so this shouldn't hinder the abilities of your programs.
|
||||
|
||||
Consider the following program executed as command block command:
|
||||
|
||||
<pre>
|
||||
/script run
|
||||
run_program() -> (
|
||||
loop( 10,
|
||||
foo = floor(rand(_));
|
||||
check_not_zero(foo);
|
||||
print(_+' - foo: '+foo);
|
||||
print(' reciprocal: '+ _/foo )
|
||||
)
|
||||
);
|
||||
check_not_zero(foo) -> (
|
||||
if (foo==0, foo = 1)
|
||||
)
|
||||
</pre>
|
||||
|
||||
Lets say that the intention was to check if the bar is zero and prevent division by zero in print, but because
|
||||
the `foo` is passed as a variable, it never changes the original foo value. Because of the inevitable division
|
||||
by zero, we get the following message:
|
||||
|
||||
<pre>
|
||||
Your math is wrong, Incorrect number format for NaN at pos 98
|
||||
run_program() -> ( loop( 10, foo = floor(rand(_)); check_not_zero(foo); print(_+' - foo: '+foo);
|
||||
HERE>> print(' reciprocal: '+ _/foo ) ));check_not_zero(foo) -> ( if (foo==0, foo = 1))
|
||||
</pre>
|
||||
|
||||
As we can see, we got our problem where the result of the mathematical operation was not a number
|
||||
(infinity, so not a number), however by pasting our program into the command made it squish the newlines so
|
||||
while it is clear where the error happened and we still can track the error down, the position of the error (98)
|
||||
is not very helpful and wouldn't be useful if the program gets significantly longer. To combat this issue we can
|
||||
precede every line of the script with dollar signs `$`:
|
||||
|
||||
<pre>
|
||||
/script run
|
||||
$run_program() -> (
|
||||
$ loop( 10,
|
||||
$ foo = floor(rand(_));
|
||||
$ check_not_zero(foo);
|
||||
$ print(_+' - foo: '+foo);
|
||||
$ print(' reciprocal: '+ _/foo )
|
||||
$ )
|
||||
$);
|
||||
$check_not_zero(foo) -> (
|
||||
$ if (foo==0, foo = 1)
|
||||
$)
|
||||
</pre>
|
||||
|
||||
Then we get the following error message
|
||||
|
||||
<pre>
|
||||
Your math is wrong, Incorrect number format for NaN at line 7, pos 2
|
||||
print(_+' - foo: '+foo);
|
||||
HERE>> print(' reciprocal: '+ _/foo )
|
||||
)
|
||||
</pre>
|
||||
|
||||
As we can note not only we get much more concise snippet, but also information about the line number and position,
|
||||
so means its way easier to locate the potential problems problem
|
||||
|
||||
Obviously that's not the way we intended this program to work. To get it `foo` modified via a function call,
|
||||
we would either return it as a result and assign it to the new variable:
|
||||
|
||||
<pre>
|
||||
foo = check_not_zero(foo);
|
||||
...
|
||||
check_not_zero(foo) -> if(foo == 0, 1, foo)
|
||||
</pre>
|
||||
|
||||
.. or convert it to a global variable, which in this case passing as an argument is not required
|
||||
|
||||
<pre>
|
||||
global_foo = floor(rand(10));
|
||||
check_foo_not_zero();
|
||||
...
|
||||
check_foo_not_zero() -> if(global_foo == 0, global_foo = 1)
|
||||
</pre>
|
||||
Reference in new issue
Block a user