First of all, every line has to be ended by a semicolon. Comments can be included in the script in the same way as in C and C++. For example,
// This is a single line comment
A = 1 + 2;
This
is a block
comment.
?A = A + 1;
The end of a script does not have to be signalled with a particular keyword. By the way, a question mark at the beginning of a line prints the result after evaluation of the corresponding line in the output window. Placing a colon (:) at the beginning of a line draws the result after evaluation of the line in the visualization window, if the result has a geometric interpretation.
In order to execute a script you have to press ctrl + p in CLUCalc. Note that you can simply copy-paste the example given in these help files into the editor window of CLUCalc and then execute them.
By the way... Before a script can be executed it has to be parsed. During parsing a code tree is generated. This code tree may then be executed any number of times. For the user, this two step process is usually hidden. The first time a user executes a script it is parsed. Subsequent executions only execute the code tree. This two-step process has the advantage that a lot of processing only has to be done once. This is particularly important in the present case, since a script may have to be executed many times per second in order to achieve an animated presentation. |
_abcdefghijklmnopqrstuvwxyzäöüß ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜ
A label may also include the characters 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, but these must not be the first character of a label. For example, "A1" is a label but "1A" is interpreted as the number 1 followed by label "A". The parser does not recognize the latter case as an error. The "Umlaute" ä, ö, ü and ß are also valid letters in a label, which allows you to use any German word as a label.
Variables do not have to be declared explicitly. Instead, when the parser encounters an unknown label, it assumes it to be a variable of type "counter", i.e. an integer, and initializes it with zero.
The type of a variable is automatically changed appropriately when it is assigned a particular value. For example,
A = 1.2; // Now A is of type 'Scalar' A = VecE3(1, 1, 1); // Now A is of type 'Multivector' A = "Hello World"; // Now A is of type 'String'
Variables can be of the following types:
counter
. This is equivalent to an integer variable. A corresponding variable is created by assigning a label a whole number not followed by a '.', eg. A = 2
.
scalar
. This is equivalent to a double variable. You can create a variable of this type by writing for example, A = 3.1415
, or A = 31415e-4
. Counter variables are automatically casted into Scalar variables if needed. However, the reverse is only possible if the value of a Scalar variable is equal to an integer value.
multivector
. Multivectors can be created using the functions VecE3
, VecP3
and VecN3
, which create multivectors of grade 1 (ie. vectors) in Euclidean, projective and conformal space, respectively. Note that you cannot apply operators to multivectors from different spaces. Nevertheless, you can use the above mentioned functions to convert multivectors from one space into another. For example,embeds vector
matrix
. Matrices can be created using the function Matrix
. This will be discussed later on.
string
. Strings are created by enclosing an arbitrary text in double quotes, e.g. A = "Hello World";
. Strings can be concatenated with the '+' operator. You can also add variables of any type to a string, which converts the variable into a string and then concatenates the strings. For example,
A = 1.2;
Text = "The result is: " + A;
Now the variable Text
contains the string "The result is: 1.2". You can include the symbol " in a string by writing \", eg.
Text = "This is \"Text\"."
color
. There exist a number of predefined color variables like Red, Green, Blue, Magenta, Cyan, Yellow, Black, White
, etc. If you want to define your own color, use the function Color
.
list
. A variable of this type contains a list of variables. These may be of different types. Nested lists are also allowed up to a level where your computer runs out of memory or processing power ;-).
vertexlist
. A variable of this type contains a list of vertices. Vertices are simply 3D-points which are used for drawing objects. A vertex list is returned by the Plot
function. In order to draw a vertex list use the colon operator.
image
. A variable of this type contains an image. This will be discussed in an extra section. Internally, the variables that are created in a script are saved in a list. This list of variables is only deleted every time the script is parsed. The variable list is not reset before every execution of the code-tree. Therefore, variables that are assigned a value during the first execution of a script, already contain this value at the beginning of the second execution of the script.
For example, you can count the number of times a script was executed by writing
?A = A + 1;
?a = 1; ?b -> a; b = 2; ?a;
has the output
a = 1 b -> 1 a = 2
In this code snippet a variable a
is created and assigned the value 1
. Then a variable b
is assigned a reference to variable a
. Now, when b
is changed then so is a
, as can be seen in the next to code lines. If a reference is made to a constant object, then the reference operator is equivalent to the assignment operator. For example,
?b -> 2;
results in
b = 2
Furthermore, when the reference operator is applied to a reference variable, no reference to the reference is created. Instead, the reference on the RHS is simply assigned to the LHS. For example,
?a = 1; // Initialize a with 1 ?b -> a; // Assign b the reference to a ?c -> b; // Also assigns c the reference to a (not b) ?b -> 0; // Sets b to 0 without changing a (or c) ?a = 2; // Set a to 2 ?c; // Now c also returns the value of a
has output
a = 1 b -> 1 c -> 1 b = 0 a = 2 c -> 2
-1.2
the minus operator is a unary operator acting on the element on its right, whereas in 2 - 1.2
the minus operator is binary. Every operator also has a certain "binding strength". An operator with a higher binding strength is executed before an operator with a lower one. In the following the various operators are described.
By the way... ... all operators apart from the assignment operator ( = ) and the print operator (? ) are recursively applied to list. See section Variable Lists for more information on that. |
Point Operators. Many of the operators described in the following also have a 'point' version. For example, there exists the operator <tt>+ but also .+
. These 'point' versions of the operators perform the same operation as the operators without the leading point (pointless operators ;-). However, when point operators are applied between two lists (see section Variable Lists) or matrices (see Working with Matrices), then the base operator version is applied between each pair of elements separately. If an operator has a point version, this is noted in the following lists.
The available binary operators are the following:
Operator | Function | LHS-Type | RHS-Type | L to R |
* , .* | (geometric) product | Multivector | Multivector | yes |
Multivector | Scalar | yes | ||
Scalar | Scalar | yes | ||
matrix product | Matrix | Matrix | yes | |
Matrix | Scalar | yes
| ||
/ , ./ | (geometric) division | Multivector | Multivector | yes |
Multivector | Scalar | yes | ||
Scalar | Scalar | yes | ||
matrix division | Matrix | Matrix | yes | |
Matrix | Scalar | yes
| ||
. , .. | inner product | Multivector | Multivector | yes
|
^ , .^ | outer product | Multivector | Multivector | yes |
Multivector | Scalar | yes
| ||
^^ | power | Scalar | Scalar | yes |
Multivector | Counter | yes
| ||
+ , .+ | addition | Multivector | Multivector | yes |
Matrix | Matrix | yes | ||
Scalar | Scalar | yes | ||
concatenation | String | all types | yes
| |
- , .- | subtraction | Multivector | Multivector | yes |
Matrix | Matrix | yes | ||
Scalar | Scalar | yes
| ||
| | join | Multivector | Multivector | yes |
OR | Counter | Counter | yes
| |
& | meet | Multivector | Multivector | yes |
AND | Counter | Counter | yes
| |
% | modulo | Scalar | Scalar | yes
|
° | grade projection | Multivector | Counter | no
|
~ | index list generation | Counter | Counter | yes
|
<< , .<< | list element concatenation | list | any type | no |
string concatenation | string | any type | no |
VecE3(1,2,3) * 1.2
but also 1.2 * VecE3(1,2,3)
. Since a variable of type Counter can always be casted to a variable of type Scalar, you can also use a Counter variable wherever you see the type Scalar in the table.The unary operators available in CLUScript are:
Operator | Function | Variable Types |
? | print element | all types |
: | draw element | Multivector, Matrix |
! | inverse | Multivector, Matrix, Scalar |
* | dual | Multivector
|
~ | reverse | Multivector |
transpose | Matrix
| |
' | main involution | Multivector
|
- | negate | Counter, Scalar |
Multivector, Matrix |
Here is a list of all binary logical operators. The return type is either a scalar set to zero or unity, depending on whether the result of the logical operation if false or true. Operations between matrices and single elements or lists and single elements return a matrix or a list, respectively. These matrices or lists then contain only zero and unity entries, depending on the result of the logical operation. Note that for all logical operations the LHS and RHS may be exchanged.
Symbol | Meaning | LHS Type | RHS Type | Return Type |
== , .== | equality | scalar | scalar | scalar |
matrix | matrix | scalar | ||
matrix | scalar | matrix | ||
multivector | multivector | scalar | ||
string | string | scalar | ||
image | color | image
| ||
!= , .!= | inequality | scalar | scalar | scalar |
matrix | matrix | scalar | ||
matrix | scalar | matrix | ||
multivector | multivector | scalar | ||
string | string | scalar | ||
image | color | image
| ||
> , .> | greater than | scalar | scalar | scalar |
matrix | scalar | matrix
| ||
< , .< | smaller than | scalar | scalar | scalar |
matrix | scalar | matrix
| ||
>= , .>= | greater or equal than | scalar | scalar | scalar |
matrix | scalar | matrix
| ||
<= , .<= | smaller or equal than | scalar | scalar | scalar |
matrix | scalar | matrix
| ||
&& , .&& | logical AND | scalar | scalar | scalar |
matrix | scalar | matrix
| ||
|| , .|| | logical OR | scalar | scalar | scalar |
matrix | scalar | matrix
|
There is only one logical unary operator.
Symbol | Meaning | RHS Type | Return Type |
!! | logical NOT | scalar | scalar |
matrix | matrix |
The binding strengths of the various operators are listed in the following table from strongest to weakest binding operator.
Operator(s) | Direction |
Functions | RL |
° (grade proj.) | LR |
* (dual), - (negate), ! , ~ , ' | RL |
% (modulo) | LR |
^ (outer product) | LR |
. (inner product) | LR |
* (geometric product), / | LR |
| (join), & (meet) | LR |
- | LR |
+ | LR
|
~ (index list) | RL
|
== (equal), != (not equal), < , > , <= , >= , | LR
|
!! (NOT) | LR
|
&& (AND) | LR
|
|| (OR) | LR
|
<< (list concatenation) | LR
|
= | RL |
: (draw), ? (print) | RL |
If a number of operators appear on the same line, they bind equally strong. In the case that a number of equally strong operators are to be executed sequentially, the second column indicates whether this is done from right to left (RL) or from left to right (LR). For example, the expression A * B * C
is equivalent to (A * B) * C
. That is, the *
operator is executed from left to right. The =
operator on the other hand is executed from right to left, i.e. A = B = C
is equivalent to A = (B = C)
.
Most operators return a temporary variable containing the result of the operation. These temporary variables are constants and cannot be assigned any value. That is, A * B = C
is not allowed. The assignment operator <tt>= returns the value of the LHS variable. Therefore, it is possible to write A = B = 1
, which first assigns 1
to B
and then to A
.
The print operator <tt>? and the draw operator <tt>: both return no value at all. Therefore, you can write :VecE3(1,1,1):Red
in order to first set the drawing color to red and then to draw the vector. Similarly, ?A ?1.2
first prints 1.2
and then the value of A
.
By the way... ... if you write ?A = 1.2 the output is A = 1.2 , and ?1.2 produces the output Constant = 1.2 . In order to obtain an output like The result is 1.2! , you need to write ?"The result is " + 1.2 + "!" . |
The syntax of if-clauses is just the same as in C/C++.
if ([condition1]) { [condition1 true branch] } else if ([condition2]) { [condition2 true branch] } else { [false branch] }
You do not need the curly brackets, if you only want to execute a single line. For example,
if (a == 1) ?"'a' is equal to one"; else ?"'a' is not equal to one";
The condition in if ([condition])
must be an expression that results in a scalar. If this scalar is zero, then the condition is considered false, otherwise it is considered true. The logical operators like '==
' return either one or zero.
The other important functionality apart from conditional branching, is that of loops, i.e. executing a certain part of code a number of times. In CLUScript the different loops like for
-loops, do-while
-loops and while
-loops are unified in a more simple syntax. The keyword loop
asks the parser to continually execute the next line or block of code following the keyword. This is done until the keyword break
is encountered. A standard for
-loop may then be implemented as follows.
// A for-loop executed 5 times // Reset the counter variable i = 0; loop { // Check whether five counts // have already been achieved. if (i >= 5) // If we are finished end the loop break; // Do something sensible // Increment the counter i = i + 1; }
If you forget to add a break
command, or never allow your code to reach a break
command, the loop would in principle run indefinitely. In order to avoid your programs to hang, CLUCalc has a maximum count of 10000 for loops after which the loops is exited in any case. A better solution to the problem would be to run the parser in an extra thread, but again this is something for a future version of CLUCalc.
[A, 1.2, Red]
is a list containing the label A
, the Scalar Red
. You can also nest lists to any level, as in [A, [B, C]]
.
There are a number of different was of generating lists. The most obvious one is to write A = [1,2,3]
to assign A
the list [1,2,3]
. Another method is to use the concatenation operator <tt><< as in the following example.
A = []; // Create an empty list
A << 1;
A << 2 << 3;
Now A
also contains [1,2,3]
. If it is known beforehand how many elements a list will have, but their entries can only be evaluated at a later stage, it is faster to create a list of the appropriate size and then set the components, instead of using the concatenation operator. This can be done with the function List(). For example,
A = List(3); A(1) = 1; A(2) = 2; A(3) = 3;
again generates the same list as before. From CLUScript v2.2 onwards, it is possible to assign to lists of variables. For example, if you want to assign the values 1, 2 and 3 to variables A, B and C, you can write
[A,B,C] = [1,2,3];
This is particularly useful when dealing with functions that return a list of values, as for example the function SVD(), which evaluates the singular value decomposition of a matrix. It return a list of three matrices, typically called U, D and V. To have direct access to these matrices you can write
[U,D,V] = SVD(M);
A = [1, 2, 3]
. This is useful since most operators are applied recursively to variable lists. The only operators that are not are =
. For example, [1, 2, 3] + 1
is equivalent to writing [1+1, 2+1, 3+1]
. Furthermore, operating with lists on lists applies the corresponding operator for every element of one list to every element of the other list, creating a nested list in the process. For example, [A, B] * [C, D]
is equivalent to [ [A*C, B*C], [A*D, B*D] ]
. This last expression is basically a
After these two lines M
contains the matrix
L1 = [1,2,3]
and L2 = [1,0,-1]
and you would like to add these two lists elementwise. That is, add the first element of L1
to the first element of L2
, etc. This can be achieved with the operator <tt>.+. Here is an example,
?L1 = [1,2,3]; ?L2 = [1,0,-1]; ?Sum = L1 + L2; ?PointSum = L1 .+ L2;
has the output
L1 = [1, 2, 3] L2 = [1, 0, -1] Sum = [[2, 1, 0], [3, 2, 1], [4, 3, 2]] PointSum = [2, 2, 2]
That is, the point summation is somewhat like vector addition, if you interpret the lists L1
and L2
as vectors. Note that the standard operator +
is applied between the elements of the lists. Therefore, this works for lists containing any elements for which the operator +
is defined. An example is,
?["Hello ", 2] + ["World", " times"]; ?["Hello ", 2] .+ ["World", " times"];
results in
Constant = [[Hello World, Hello times], [2World, 2 times]] Constant = [Hello World, 2 times]
This behaviour is the same for all point operators. However, we should probably say something about the list concatenation operator <tt><< and its point version .<<
. The operator <tt><<
concatenates the element on its left to the list on its right. For example,
?L = [1,2,3];
L << 4;
?L;
gives
L = [1, 2, 3]
L = [1, 2, 3, 4]
This also works for lists on the RHS.
?L = [1,2,3];
L << [4,5];
?L
L << [[6,7], "Hello"];
?L;
which gives
L = [1, 2, 3]
L = [1, 2, 3, 4, 5]
L = [1, 2, 3, 4, 5, [6, 7], Hello]
The point version of this operator concatenates element wise if both lists have the same number of elements and if the elements are lists themselves. For example,
?L1 = [1, 2, 3];
?L2 = [4, 5, 6];
?L12 = L1 .<< L2;
gives
L1 = [1, 2, 3]
L2 = [4, 5, 6]
L12 = [1, 2, 3, 4, 5, 6]
whereas
?L1 = [[1], [2], [3]];
?L2 = [[4], [5], [6]];
?L12 = L1 .<< L2;
gives
L1 = [[1], [2], [3]]
L2 = [[4], [5], [6]]
L12 = [[1, 4], [2, 5], [3, 6]]
Elements of variable lists, vertex lists, matrices and images can be retrieved using the round brackets '()
' as in a function call. Applied to a list as in [1,2](2)
, the operator returns the second element in the list. Applied to a nested list it returns the appropriate sublist: [[1,2], [3,4]](2)
returns [3,4]
. You can also apply the operator as many times as the list is nested. For example, [[1,2], [3,4]](2)(2)
returns 4
and so does [[1,2], [3,4]](2,2)
.
Elements of a list can be modified as shown in the following example.
?L = [1,2,3,4];
L(2) = 10;
?L;
has output
L = [1, 2, 3, 4]
L = [1, 10, 3, 4]
Note that
. For example,?L(2)
has as output Constant -> 10
instead of Constant = 10
, since the return value of L(2)
is the reference to the second element in the list and not just the value. You can also store the reference to an element of a list using the reference operator <tt>->
?L = [1,2,3,4];
?a -> L(2);
a = "Hello";
?L;
generates the output
L = [1, 2, 3, 4]
a -> 2
L = [1, Hello, 3, 4]
This functionality can be quite useful if you need to access the same element in a list a large number of times, since it is much slower to evaluate the reference to an element of a list (as in
L(2)
), than to use the reference to an element directly.
If you have created a reference to an element of a list and then destroy the list, for example, by overwriting the respective variable with a different value, the reference variable is set to zero. Here is an example,
?L = [1,2,3,4]; // Create a list and assign it to L
?a -> L(2); // Assign to a the reference to the second element in L
L = 0; // Overwrite L with an integer value. The list from above
// does not exist anymore now, and thus the reference
// stored in a is invalid.
?a; // Hence, a is set to zero.
generates the output
L = [1, 2, 3, 4]
a -> 2
a = 0
By the way...
... a list need not only consists of element of one type. In particular, you can overwrite elements of a list with new elements of a different type.
Lists of a particular structure can be created by selecting elements of a list by a string. For example,
L = []; // Create an empty list
L("Hello") = 1; // Create an element with name 'Hello'
?L; // Look at the list
?L("Hello"); // Get value of element 'Hello'
has as output
L = [[Hello, 1]]
Constant -> 1
As this short script shows, when you select an element with a name that does not exist in the list, then this element is created. If it does exist, then a reference to its value is returned. The function AnalyzeMV() returns such a type of list, for example.
generates the output
L = [plane, [mag, 1.41421], [pos, [ 1^e1 ]], [dir1, [ 1^e2 ]], [dir2, [ 1^e3 ]]]
Constant -> [ 1^e1 ]
Constant -> [ 1^e2 ]
Constant -> [ 1^e3 ]
This way of creating lists is somewhat similar to structs in C and C++.
You can also extract more than one element of a list at a time by passing an index list through the selection operator. It's easiest to understand this property by showing a couple of examples. The script
?L = [[1,2], [3,4]];
?L(1);
?L([1]);
?L([[2], [1]]);
?L([[1,2], [2,1]]);
generates the following output
L = [[1, 2], [3, 4]]
Constant -> [1, 2]
Constant = [1, 2]
Constant = [[3, 4], [1, 2]]
Constant = [2, 3]
You can also use the operator <tt>~
to generate an index list starting at the value given on the LHS up to the value on its RHS in interger steps of 1. For example,
?1 ~ 5;
results in
Constant = [[1], [2], [3], [4], [5]]
This may, for example, be used in the following way.
L = [1,2,4,8,16];
?L(2~4);
generates the output
Constant = [2, 4, 8]
Although this is jumping ahead a little, using lists you can rotate a list of multivectors as follows.
// Create special variables for E3
// like e1, e2, e3.
DefVarsE3();
// Define some vectors
A1 = e1; A2 = e2; A3 = e3;
// Define a rotor, rotating about
// the e2-axis, 20 degrees.
R = RotorE3(0, 1, 0, 20 * RadPerDeg);
// Create a list of the basis vectors
Basis = [A1, A2, A3];
// Rotate all basis vectors
RBasis = R * Basis * ~R;
// And finally create a rotation matrix
// from the bases.
M = Matrix(Basis . RBasis);
RBasis
contains a list of the rotated basis vectors, and M
is the transformation matrix from basis Basis
to basis RBasis
.
You can do a couple of more things than just display them from CLUScript v2.0 onwards. As you have probably gathered by now, strings can be concatenated using the operator +
. Given a variable containing a string, you can also concatenate elements to this string using the operator <tt><<
. For example,
?s = "Hello "; s << "World" << 2 << 3; ?s;
which outputs
Hello Hello World23
A character in a string can be selected using the selection operator <tt>(). For example,
?s = "Hello";
?s(3);
?s([[2], [4], [1]]);
gives
Hello l Constant = [e, l, H]
You can also search for all positions of a substring in a string as follows.
?s = "Hello World"; // The string ?s("l"); // All positions of 'l' in the string s ?s("Wo"); // All positions of 'Wo' in the string s
This script returns
Hello World Constant = [3, 4, 10] Constant = [7]
This feature can be quite useful when you want to strip, for example, a filename returned from FileChooser() from its path and extension.
{ }
) as follows.
// Define a macro with the name Hello Hello = { ?"Hello"; } // Execute the macro Hello();
This script produces the following output.
Hello
The variable Hello
does in fact contain a reference to the actual function. This means that you can also store the reference to the function in a list. For example,
// Define a list L = [1,2,3]; // Define a macro and store a reference to it in the list L L(2) = { ?"Hello"; } // What is L? ?L; // Execute the macro L(2)(); /endcode which produces the outpu \code L = [1, 030850C8, 3] Hello
Note that a macro is not executed at the point of its definition. It is also only translated once in the parsing process, such that a repeated execution of a script will not redefine a macro continuously.
You can also pass any number of parameters to a macro. There is no internal process that checks whether the number and type of parameters is correct, which means that you also do not have to declare the number and type of parameters expected when you define a macro. Parameters are passed to a macro in the same way as to any other function: a comma separated list within the round brackets. That is, Hello(1,b)
passes the parameters 1
and b
to the macro. Whether the macro uses these parameters or not, does not matter. Within the macro a variable list called _P
is always available, which contains the parameters passed to the macro. You can access the different parameters with the selection operator <tt>(), just as for any other variable list. Another code example should clarify this.
// Define a macro with the name Hello Hello = { ?"Hello " + _P(1); } // Add the first parameter to the string // Execute the macro // The following type of call would cause an error, // since the macro uses the first parameter, which // does not exist. // Hello(); // This is how you would use the macro 'Hello' Hello("World"); // The following does not cause an error. // The macro simply ignores the second parameter. // Hello("World", 2);
generates the output
Hello World
If you define variables in a script, then they are strictly local. That is, in CLUScript v2.0 you can no longer access global variables by simply using their variable name in a function. For example,
a = 1; // global variable a func = // define macro { ?a; // This creates a local variable a ?a = 2; // Set local 'a' to 2 ?b = 1; // Create local variable b } func(); // execute macro ?a; // global variable 'a' ?b; // global variable 'b'
The output of this script is
a = 0 a = 2 b = 1 a = 1 b = 0
The variables defined inside the macro only exist within the macro and also do not relate to global variables of the same name. You can, however, refer to global variables from within a macro by writing ::
in front of the respective variable name. For example, here is a modified version of the previous script.
a = 1; // global variable a func = // define macro { ?a = 1; // This creates a local variable 'a' ?::a = 2; // Set global 'a' to 2 ?a; ?::b = 1; // Create global variable b } func(); // execute macro ?a; // global variable 'a' ?b; // global variable 'b'
This time the output of the script is
a = 1 a = 2 a = 1 b = 1 a = 2 b = 1
::
from within macros.::
in front of the macro name. Here is an example,
func1 = { ?"Hello"; } func2 = { // Define a local version of func1 func1 = { ?"Hola"; } func1(); // Call local macro stored in func1 ::func1(); // Call global macro stored in ::func1 ?"World"; } func2(); // Execute func2
which produces the output
Hola Hello World
Of course, a macro can contain more than a single line. As shown above, you can write a whole block of code, with lines separated by semicolons, within the curly brackets. The return value of a macro is the result of the last command line, if this line does not end with a semicolon. For example,
// Define a macro with the name Rot2D // to rotate 2d-coordinates of a point. // Expected parameters: (x, y, angle) Rot2D = { // Copy the parameters x = _P(1); y = _P(2); angle = _P(3); // Rotate a = x*cos(angle) - y*sin(angle); b = x*sin(angle) + y*cos(angle); // Return the pair (a, b) (a, b) } // Now we can rotate in 2D ?Rot2D(1, 0, 45*RadPerDeg);
The output of this script is
Constant = (0.707107, 0.707107)
You can also return different values at different places of a script using the break
keyword. Here is an example,
func = { // If the first parameter is negative then // return -1 if (_P(1) < 0) (-1) break; // otherwise return the square root of the first parameter sqrt(_P(1)) } ?func(-4); ?func(4);
which returns
Constant = -1 Constant = 2
Another new feature of CLUScript v2.0 is that variables are passed to macros by reference. This means that you can change parameters that are passed to a function. Here is an example,
// This macro increments the variable passed Incr = { a -> _P(1); // Get reference to first parameter a = a + 1; // Increment first parameter } ?x = 1; Incr(x); ?x;
which returns
x = 1 x = 2
References to parameters are also useful if you need to access the parameters at many places in a macro.
Macros are particularly useful if you want to execute the same complex calculation at different places in the script. They can also make the Plot
command better readable.
Another nice features of macros is that you can also pass them as parameters to other macros. This works because if you define func = { //... }
, then func
is simply a variable that contains a referecnce to the code enclosed in the curly brackets. The reference itself is of no interest, but it can be treated like a standard variable. In the next example we pass one function to another one.
_2dView = 1; _BGColor = White; DefVarsE3(); // Define my own plotting of functions MyPlot = { // The first parameter needs to be the function f = _P(1); if (Type(f) != "code") { // Error message ?"Expect function as first parameter."; // end this macro break; } // The minimum, maximum and step values minval = _P(2); maxval = _P(3); step = _P(4); // Loop over positions in range and add // function values to list. pos = minval; Data = f(pos); loop { pos = pos + step; if (pos > maxval) break; Data << f(pos); } // Draw the list of data points DrawPointList(Data); } // The function we want to plot func = { x -> _P(1); // Return value x*e1 + (1 - x + sin(x*x))*e2 } :Black; // Now plot the function MyPlot(func, -2, 2, 0.1);
which produces the visualization
A macro like MyPlot
may be stored in a separate file and included in a script via the include
preprocessor directive (see Preprocessor Directives), whenever it is needed.
It is also possible to do a kind of object oriented programming using structured lists. Have a look at the following script.
func = { this -> _P(1); ?"The name is '" + this("name") + "'"; } C = []; C("name") = "My first list"; C("print") = func; // Execute macro 'func' and pass as first parameter // the structured list itself. C("print")(C);
This produces the output
The name is 'My first list'