Vagrearg Logo  

gcmc - G-Code Meta Compiler

Syntax description

Gcmc is a script language with late binding and lazy evaluation, meaning that run-time execution uncovers variable mismatches and all expressions are first known/converted to values when they are executed.

Comments are like C/C++, where /* */ indicate block comments and // is a comment to end-of-line.


Variables are typed with following available types:
undefined An undefined quantity.
integer Any whole number positive or negative.
floating point Any number containing a decimal point. Floating point numbers less than |1e-12| are considered to be zero.
scalar Any integer or floating point type is considered to be scalar.
vector A list of scalar values. Any coordinate in a vector may be undefined and a vector may be empty. Vectors may be indexed to obtain the individual scalar values. Examples:
[1, 2.0]X is integer 1, Y is floating point 2.0
[-, -, -5.0]X and Y are undefined, Z is floating point -5.0
vector-list A collection/list of vectors. Vector-lists may be empty. Vector-lists may be indexed to obtain the individual vectors. Example: { [0, 0, 0], [-, 2, 3] }
string A collection of UTF-8 source characters delimited by double quotes ("). Standard backslash escape sequences are supported, including octal and hex escapes. Embedded nul-characters are not allowed.
The internal representation of the characters is in Unicode (wchar_t). Beware that Windows limits wchar_t to 2 bytes by default (U+0000...U+FFFF), whereas most *nix versions will be able to represent full UCS-4 (U+00000000...U+7FFFFFFF). A warning is emitted in pedantic mode if the representation exceeds the current Unicode defined map at U+10FFFF, or exceeds the representation available for the machine/OS.
"This is a string with \101scapes in \x44ifferent styles\n"
Integer values may be entered in decimal (default) or hexadecimal format using "0x" prefix. I.e. 0x0a equals 10.


All scalar types can have units associated. Available units are:
inInch (25.4mm)
milMil = 1/1000 inch (0.0254mm); always converted to inch
degDegrees, 360° full circle
radRadians, 2π full circle
 No units (none)

  • 10.34mm
  • 200.7mil (becomes 0.2007in)
  • 1.125in
  • 5432
  • 60deg
  • 1.57079632679rad
Note that mils ("mil") are always converted to inch as soon as the value is parsed.

Calculations with units is implicit in the grammar. The resulting units are derived from the left-hand side of the expression, with the following rules (<none> indicates no unit):
<none>+-*/%<none>→ <none>
<none>+-*/%mm→ mm
<none>+-*/%in→ in
<none>+-*/%deg→ deg
<none>+-*/%rad→ rad
mm+-*/%<none>→ mm
mm+-*%mm→ mm
mm/mm→ <none>
mm+-*%in→ mm
mm/in→ <none>
mm+-*/%deg→ Warning [mm]
mm+-*/%rad→ Warning [mm]
in+-*/%<none>→ mm
in+-*%mm→ in
in/mm→ <none>
in+-*%in→ in
in/in→ <none>
in+-*/%deg→ Warning [in]
in+-*/%rad→ Warning [in]
deg+-*/%<none>→ deg
deg+-*/%mm→ Warning [deg]
deg+-*/%in→ Warning [deg]
deg+-*%deg→ deg
deg/deg→ <none>
deg+-*%rad→ deg
deg/rad→ <none>
rad+-*/%<none>→ rad
rad+-*/%mm→ Warning [rad]
rad+-*/%in→ Warning [rad]
rad+-*%deg→ rad
rad/deg→ <none>
rad+-*%rad→ rad
rad/rad→ <none>

Only mm/in, in/mm, deg/rad and rad/deg conversions will affect the actual magnitude of the values on which the operation is performed. Examples:
10mm + 200mm→ 210mm
10mm + 200in→ 5180.0mm(converted to floating point)
10mm + 200mil→ 15.08mm(implicit floating point due to mil)
10mm + 200→ 210mm
10 + 200→ 210
10in + 200→ 210in
10in + 200mm→ 17.874015748in(converted to floating point)
10in + 200in→ 210in
10in + 200mil→ 10.2in(implicit floating point due to mil)
1deg + 1deg→ 2deg
1deg + 1rad→ 58.29577951deg(converted to floating point)
1deg + 1→ 2deg
1 + 1→ 2
1rad + 1deg→ 1.01745329rad(converted to floating point)
1rad + 1rad→ 2rad
1rad + 1→ 2rad

On output, all units are converted to millimeters or inches for axes XYZ and UVW (depending -i option). Axes ABC will have units converted to degrees. All values with no units associated are treated as if they were millimeters/degrees or inches/degrees as appropriate for the respective axis depending the -i option.

Important note: Portable programs between imperial and metric mode must use units consistently. Omitting units on some values may cause calculations to be thrown off by a large factor due to implicit conversions of values with no units associated. It is always good practice to use units consistently throughout the entire program.


Calculations are performed with unary and binary operators with following precedence:
2[] . expr++ expr--left-to-rightvector/vector-list index, vector field, post-increment, post-decrement
3! ~ + - ++expr --exprright-to-leftunary: logical not, binary not, plus, minus, pre-increment, pre-decrement
4* / %left-to-rightmultiplication, division, modulo
5+ - +| -|left-to-rightbinary plus, minus, plusor and minusor
6<< >>left-to-rightshift left, shift right
7> < >= <=left-to-rightcomparison
8== !=left-to-rightcomparison
9&left-to-rightbinary and
10^left-to-rightbinary xor
11|left-to-rightbinary or
12&&left-to-rightlogical and
13||left-to-rightlogical or
14?:right-to-leftternary/conditional expression
15= += -= +|= -|= *= /= %=
<<= >>= |= &= ^=

Valid operations and result:
vectorlist+vectorlistvectorlist (append)
string+scalarstring (printf scalar)
string+vectorstring (printf vector)
string+vectorliststring (printf vectorlist)
string+stringstring (concat)
vector*vectorscalar (dot product)
vector|vectorvector (merge values on left-hand side)
vector&vectorvector (replace values on left-hand side)
Operators +| and -| work the same as + and - with the exception of how undef values are handled. See below for details.

Integer vs. floating point

Gcmc makes a distinction between integer and floating point values. Calculations performed on integers give integer result. Calculations on floating point or mixed integer/floating point gives floating point result. This behavior is regardless associated units unless unit-conversion is implied.

As a consequence, all operations with integers, including divisions, result in integers and are therefore implicitly rounded calculations. This can give rise to problems in a program if exact results are required. You should always use floating point values is you require exact results.
1   / 10   → 0		// Integer result
1.0 / 10   → 0.1
1   / 10.0 → 0.1
1.0 / 10.0 → 0.1

1mm   / 10 → 0mm	// Integer result
1.0mm / 10 → 0.1mm

1mm + 1in  → 26.4mm	// Implicit conversion to floating point

EPSILON calculation

All floating point numbers are considered equal if they are within |1e-12| of each other. The value 1e-12 is called EPSILON and is used in all comparisons as well as (internal) conversions from floating point to integer. Limiting the precision of floating point has the advantage of allowing rounding errors to be disregarded more easily. This is especially important when calculating arcs, where otherwise small errors may cause the arc radius versus endpoint calculation to fail.
a = 0;
(a + 1.0e-12) == a	// false
(a + 0.9e-12) == a	// true
The value 1e-12 allows for femto-meter accuracy and is four..five orders of magnitude better than the covalent bond-radius of atoms, which, for all practical purposes and intents, should be enough for a mill.

All implicit conversions from floating point to integer also use EPSILON calculation with the following rule: n-EPSILON < n < n+EPSILON. A warning is emitted if the value of n is outside the EPSILON range whenever implicit conversions takes place. Rounding on explicit conversion (using the to_int() function) will not generate a warning, but a value within EPSILON range of the nearest integer value will be converted to that nearest integer value.
a = 1.0;
to_int(1.0 + 0.9e-12);	// 1 within EPSILON
to_int(1.0 - 0.9e-12);	// 1 within EPSILON
to_int(1.0 + 1.0e-12);	// 1 truncated
to_int(1.0 - 1.0e-12);	// 0 truncated

Undef handling

Undefined values may be used in some calculations, resulting in a real value or undefined. Rules for calculating with undefined are:
undef+|xx (if x is scalar, else error)
undef-|x-x (as 0-x)
undef*xundef (if x is scalar, else error)
undef/xundef (if x is scalar, else error)
undef%xundef (if x is scalar, else error)
The rules also apply to vector operations +, -, +| and -| where both values are vectors, or scalar multiplication/division of a vector/vector-list. Such cases will operate on the vector's coordinates using above rules.

You can assign a literal undef to a variable by using the following notations:
undefvar = [-][0];
undefvar = undef();
The literal undef is actually a vector with one undefined coordinate from which you request the first entry, which happens to be undef.

Local variables in functions are undef when declared without an assignment.

Please note that the undef interpretation for +| and -| has consequences when translating vectors and vectorlists by a fixed offset. The calculated sum/difference is not undef preserving, which may cause unintended side-effects when subsequent function calls interpret undef values with special care. For example, a move() or goto() with an undef coordinate omits the coordinate in its output entirely. Adding an offset to the vector may convert an undef to a value, which is then translated into movement, which may not be appropriate.

Add and subtract operators

Addition and subtraction on scalars, vectors and vectorlists are handled as expected when operating on integer or floating point values. Care must be taken when handling undef values.

Gcmc version 1.5.0 has changed the + and - operator's behavior to be an exclusive add and subtract and introduced the operators +| and -| to be inclusive add and subtract. Inclusive add/subtract treats the left-hand side as zero if it is undef, whereas exclusive add/subtract treats operations on left-hand side undef values as undef result. The difference between the operator's functionality can be illustrated as follows:
Exclusive Add (+),
left-hand side undef is retained, right-hand side undef substituded by 0
[15, -, -2] + [-, 10] is: or:
[15,  -, -2] [15,  -, -2]
+[ -, 10] + [ 0, 10,  0]

[15,  -, -2] [15,  -, -2]
Inclusive Add (+|),
both left- and right-hand side undef substituted by 0
[15, -, -2] +| [-, 10] is: or:
[15,  -, -2] [15,  0, -2]
+|[ -, 10] +|[ 0, 10,  0]

[15, 10, -2] [15, 10, -2]
Exclusive Subtract (-),
left-hand side undef is retained, right-hand side undef substituded by 0
[15, -, -2] - [-, 10] is: or:
[15,  -, -2] [15,  -, -2]
-[ -, 10] - [ 0, 10,  0]

[15,  -, -2] [15,  -, -2]
Inclusive Subtract (-|),
both left- and right-hand side undef substituted by 0
[15, -, -2] -| [-, 10] is: or:
[15,   -, -2] [15,   0, -2]
-|[ -,  10] -|[ 0,  10,  0]

[15, -10, -2] [15, -10, -2]

You should be aware that reversing the left-hand side and the right-hand side gives a different result:
• The exclusive add operation is not commutative (a+b is not equal b+a) and is undef preserving.
• The inclusive add operation is commutative (a+b equals b+a) and is undef replacing.
The difference is illustrated by following example and you should compare it to the example above:
Exclusive Add (+),
left-hand side undef is retained, right-hand side undef substituded by 0
[-, 10] + [15, -, -2] is: or:
[ -, 10] [ -, 10,  -]
+[15,  -, -2] + [15,  0, -2]

[ -, 10,  -] [ -, 10,  -]
Inclusive Add (+|),
both left- and right-hand side undef substituted by 0
[-, 10] +| [15, -, -2] is: or:
[ -, 10] [ 0, 10,  0]
+|[15,  -, -2] +|[15,  0, -2]

[15, 10, -2] [15, 10, -2]

Boolean logic and comparison operators

Boolean operations result in an integer value with no units of either 1 (true) or 0 (false). Boolean AND (&&) and OR (||) are evaluated using short-circuit evaluation. I.e. the right-hand side is not evaluated if the left-hand side of the expression pre-determines the outcome.
  • Any scalar value not zero (0) within |EPSILON| is considered to be true
  • Undef values are considered to be false
  • Vector are true if they contain at least one coordinate, regardless what the coordinate contains
  • Vectorlists are true if they contain at least one vector, regardless what the vector contains
  • String values are true if not empty and false if empty
Comparison operators operate on scalars, vectors and strings, with the limitation that vector comparison only supports == and !=. Scalar comparison tests the units of the scalars and emits a warning if a mismatch is detected. Strings are compared using case-sensitive comparison at Unicode character-level using the wcscmp() C-function.

Comparing vectors requires them to have the same number of entries. Comparing vectors with unequal number of entries results in a warning and the comparison result is always false. Each vector entry obeys the same unit rules as for scalars and warnings are emitted on mismatches. An undef vector entry is only equal to another undef entry.

Binary Boolean operators

Binary operators '&', '|', '^' and '~' on scalars only work on integers with no associated units. Floating point values are converted to integer and units are stripped when encountered. A warning is emitted if floating point values are used or units are encountered. An integer has at least 32 bit resolution, but may also be 64 bit wide. Therefore, the binary not (~) operator may differ depending platform. However, you can use one's (or two's) complement math to know the actual value.
val = (1<<2) | (1<<4);	// val = 20 (0x14)
val = 0x5a & 0x0f;	// val = 10 (0x0a)
val = ~1;		// val = -2 (= -(1) - 1)

Binary operators on vectors merge ('|') and replace ('&') values from the right side vector into the left side vector. Merging values only occurs on entries that are undef on the left side and not undef on the right side. Replacing values occurs for all values where a non-undef value exists in both left and right side.
[-, 2, 3] | [4, 5] → [4, 2, 3]	// Note: [4, 5] equals [4, 5, -] in this context
[1, -, 3] | [4, 5] → [1, 5, 3]
[1, 2, -] | [4, 5] → [1, 2, -]
[-, 2, 3] & [4, 5] → [-, 5, 3]
[1, -, 3] & [4, 5] → [4, -, 3]
[1, 2, -] & [4, 5] → [4, 5, -]

Index [] operator

Index operator [] works on both lvalue and rvalue. Index values must be scalar and should have no units associated. Negative indices address the vector or vectorlist from the end. Stronger restrictions apply to lvalues than rvalues, where lvalues must address a variable. Both single and double indexing is supported. Double indexes can only be performed on vectorlists. Lvalue indices may address locations that are not yet assigned, whereas rvalue indices result in a warning if the index is out of bounds. Examples:
vector = [1, 2, 3];
vector[2] = 6;			/* → [1, 2, 6] */
vector[3] = vector[-1];		/* → [1, 2, 6, 6] */
vector[7] = 2;			/* → [1, 2, 6, 6, -, -, -, 2] */
vlist = {};
vlist[2] = [1, 2];		/* → { [], [], [1, 2] } */
vlist[1][3] = 3.1415;		/* → { [], [-, -, -, 3.1415], [1, 2] } */
/* Rvalue may be constant expression */
myvec = {[1,2], [2,3]}[1];	/* → [2, 3] */
myval = [1, 2, 3][1];		/* → 2 */

Field . operator

All vectors may have the first nine entries addressed as fields for more natural readability. The field-names correspond with the axis-name for the entry in lower case letters (x, y, z, a, b, c, u, v, w).
Field addressing a vector is translated into an index operation, as explained above and follow the rules of indexing. Examples:
vector = [1, 2, 3];
call_xy(vector.x, vector.y);	/* Same as call_xy(vector[0], vector[1]); */
vector.z = 6;			/* → [1, 2, 6] */
vector.a = vector[-1];		/* → [1, 2, 6, 6] */
vector.v = 2;			/* → [1, 2, 6, 6, -, -, -, 2] */

Shift operators

Shift operators << and >> work as usual on scalars in which << multiplies by 2 and >> divides by 2, without modifying associated units. Left shift on vectors and vectorlists will delete values from the start. Right shift on vectors adds undef values to the start and on vectorlists it will add empty vectors at the start. Shift operators always return the left-hand side type, including its units. The right-hand side should not have any units associated. Examples:
1 << 24
6 >> 13
[1, 2] << 1[2]
[1, 2] >> 2[-, -, 1, 2]
{[1,2], [3,4]} << 1{[3,4]}
{[1,2], [3,4]} >> 1{[], [1,2], [3,4]}

Ternary operator

Ternary operator ?: for conditional expressions does not perform any checks on the return-type of the true- and false-clauses. This means that the expression may evaluate to different types depending the condition. This may or may not be useful, so you should beware when using ternary operators.

Dot product

The dot product of two vectors will have units associated if either vector has any entry with distance units. The resulting units are set to millimeters or inches depending gcmc's operating mode (-i option). The dot product multiplication/sum sequence will perform conversion to the appropriate distance unit on the vector entries before multiplication is performed.

Beware: the magnitude of the dot product depends on the units selected. Calculating a dot product with angular units will cause a warning to be emitted. If all vector entries are unit-less, then the result will also remain unit-less.
Running in metric mode:
vnn = [2.0, 2.0];
vmm = [1.0mm, 2.0mm];
vin = [2.0in, 1.0in];
dotp = vnn * vnn;	//    8.00000000
dotp = vnn * vmm;	//    6.00000000mm
dotp = vnn * vin;	//  152.40000000mm
dotp = vmm * vmm;	//    5.00000000mm
dotp = vin * vin;	// 3225.80000000mm
dotp = vmm * vin;	//  101.60000000mm
dotp = vin * vmm;	//  101.60000000mm
Running in imperial mode (-i command-line option):
vnn = [2.0, 2.0];
vmm = [1.0mm, 2.0mm];
vin = [2.0in, 1.0in];
dotp = vnn * vnn;	// 8.00000000
dotp = vnn * vmm;	// 2.15748031in
dotp = vnn * vin;	// 6.00000000in
dotp = vmm * vmm;	// 1.00620001in
dotp = vin * vin;	// 5.00000000in
dotp = vmm * vin;	// 2.07874016in
dotp = vin * vmm;	// 2.07874016in
Portable use of the dot product may pose a challenge if used carelessly and can result in unforeseen problems. Most uses of the dot product involve extracting the cos(φ) part (the angle between the vectors), in which case you will not have too many problems:
vmm = [1.0mm, 2.0mm];
vin = [2.0in, 1.0in];
cosphi = (vmm * vin) / (length(vmm)*length(vin));	// CORRECT: 0.8 for both metric and imperial mode
Note: The division by the length of both vectors is in parenthesis () to ensure that the result has no units. Two separate divisions would wrongly propagate the units from the second division to the result.

If vector coordinates with and without units are combined, then there will be a difference due to default conversions and a wrong result may be calculated:
vmm = [1.0, 2.0mm];	// Note the lacking unit in first coordinate
vin = [2.0in, 1.0in];
cosphi = (vmm * vin) / (length(vmm)*length(vin));	// WRONG: 0.8 for metric and 0.92677230 for imperial mode
You can retrieve the cos(φ) value more easily by using normalized vectors. Normalizing the vectors eliminates the division. However, you should still ensure that the source vectors have units on all coordinates:
vmm = normalize([1.0mm, 2.0mm]);	// vmm = [0.44721360, 0.89442719]
vin = normalize([2.0in, 1.0in]);	// vin = [0.89442719, 0.44721360]
cosphi = vmm * vin;	// CORRECT: 0.8 for both metric and imperial mode

String operations

Strings can be added using the + operator to concatenate the strings. Conversion to string is performed if the right-hand side of the + operator is scalar, vector or vectorlist. Comparing strings uses a binary Unicode character-level compare, is case-sensitive and unaware of Unicode's internals or specific character sets.
i = 1;
v = [1, 10mm, 2.0in];
str = "Hello" + " " + "World!";	// "Hello World!"
str = "val=" + i;		// "val=1"
str = "val=" + v;		// "val=[1,10mm,2.00000000in]"

Variables, statements and expressions

A variable is any word starting with a letter or _ and followed by letters, numbers or _ that has not been reserved as a keyword. Variables can be assigned values in statements. Each statement is terminated with a semi-colon (;).
Reserved words are:
break break any loop construct
const declare variable as constants
continue continue to start of loop
return return from function
for for loop construct
foreach foreach loop construct
while while loop construct
do do/while loop construct
repeat repeat loop construct
if conditional
elif else-if conditional
else final conditional clause
function function definition
local local variable scope declarator
include include other file
in inch measurement modifier
mil mil measurement modifier (0.001")
mm millimeter measurement modifier
deg degree measurement modifier
rad radians measurement modifier
All statements consist of an expression. An expression can be a constant, a variable or any combination with an operator. Assignments are also expressions, which allows cascade-able assignments, and are evaluated strictly right-to-left. Assignments have restrictions on the lvalue expression they can address while they accept any rvalue expression. Lvalues are variables and indexed variables. Rvalues may be any expression. Examples:
var123 = [1, 2, 3, 4, 5];
_xx  = 4.5mm;
yy   = 50mil;
vec  = [_xx, yy];
list = { vec, [_xx, yy], [1, 2, 3] };
a    = vec[0];   	/* a equals value of _xx (4.5mm) */
b    = list[2];  	/* b equals [1,2,3] */
val1 = val2 = val3 = 0;	/* cascaded assignment */


Variables may be declared as constant using the const keyword. The declaration must include an assignment from an expression which results in a value. Any subsequent assignment to constants is prohibited and results in a run-time error. Constants may be passed as reference in function calls, but any assignment to the local reference will then be flagged as an error. Variables declared const are local to the scope in which they are declared, just like variables declared using local in functions.
const constinteger = 1234;	/* Arbitrary constants */
const constfloat   = 5.678;
const constvector  = [1, -, 2];
const constlist    = {[1], [2]}

constinteger = 7890;	/* Error: cannot (re-)assign to constants */

/* Multiple declaration may be combined with comma as separator */
const FLAG_UP   = 0x01, FLAG_DOWN  = 0x02;	/* Flags as constants */
const FLAG_LEFT = 0x04, FLAG_RIGHT = 0x08;

Predefined constants

Gcmc defines a set of constants before any command-line defines are parsed and before the script is executed. The constants are useful for making calls to functions more readable. The function reference states the names of the constants in the description of the arguments.

The following table shows all other constants currently defined by gcmc:
GCMC_VERSION_STR string Complete version number with dots
GCMC_VERSION integer Complete version number calculated as "(major * 1000 + minor) * 1000 + point"
GCMC_VERSION_MAJOR integer Version number major part
GCMC_VERSION_MINOR integer Version number minor part
GCMC_VERSION_POINT integer Version number point release

Flow control

Program flow is controlled by standard conditional and loop control statements:
  • if(cond) { ... }
  • if(cond) { ... } elif(cond) { ... } [elif*]
  • if(cond) { ... } else { ... }
  • if(cond) { ... } elif(cond) {} [elif*] else { ... }
  • foreach(list; ident) { ... }
  • for(stmt; cond; stmt) { ... }
  • while(cond) { ... }
  • do { ... } while(cond);
  • repeat(scalar) { ... }
  • repeat(scalar; ident) { ... }
  • return expr;
  • return;
  • break;
  • continue;
The curly braces are mandatory and part of the control statement.

The continue statement short-circuits the loop and immediately jumps to the loop start. Continue in for() loops will execute the increment part of the loop prior to testing the condition. Loops may be broken by return and break statements. A return without value/expr returns a variable that will return true on test isundef(). See functions below.


Conditionals start with an if() clause and may include as many elif() clauses as you need. Optionally they may end with an else clause. The arguments to if() and elif() are evaluated to boolean expressions. Examples:
if(!isvector(val)) {
	error("val should be a vector");

if(value >= 43) {
	/* bla */
} elif(value < 0) {
	/* Negative bla */
} elif(value == 5) {
	/* the right bla */
} else {
	/* None of the above bla */


The foreach() construct expects two semi-colon separated arguments where the first is a vector or vector-list type and the second an identifier (loop-variable). The identifier is assigned a copy of each vector from the vector-list, or scalar from the vector, before executing the loop content. The loop-variable may contain en empty vector or undef if the source vector-list or source vector contains them. Example:
list = { [0, 0], [1, 0], [1, 1], [0, 1] };
foreach(list; v) {
foreach([1, 7, 3, 6, 4, 3, 1, 9, 0, 6, 4]; i) {


The for() construct expects three semi-colon separated arguments where the fist indicates the initialization, the second is the loop condition and the third argument an increment statement. Both the initialization and the increment statement may be omitted, in which the for() behaves exactly like a while() loop. Example:
for(i = 0; i < 10; i++) {


The while() construct expects one arguments that functions as the loop condition. The loop repeats for as long as the condition evaluates to true. Example:
while(complex_algo_check()) {
	complex_algo_update(arg1, arg2);


The do/while() construct expects one arguments that functions as the loop condition. The loop repeats for as long as the condition evaluates to true and executes at least once. The while() clause must be terminated with a semicolon. Example:
do {
	res = complex_algo_update(arg1, arg2);
} while(res > limit);


The repeat construct is a simple loop that repeats the loop a number of times indicated by the first argument, which must result in an integer scalar value. Both positive and negative repeats are allowed. The number of loops in a repeat is the absolute value of the scalar. An optional second argument to repeat exposes the loop-variable, which will count 1, 2, 3,... for positive repeats and -1, -2, -3,... for negative repeats.
Repeat loops should specify a scalar of integer value and no units associated. NOTE: A floating point scalar to indicate the number of loops is bound to EPSILON calculation and is considered to be of integer value when the nearest integer is within EPSILON. I.e. a loop count will be n for all values n-EPSILON < n < n+EPSILON. A warning will be issued outside this range and the scalar will be truncated. Example:
repeat(5) {
	move_r([-, 1]);		/* Incremental move in Y 5 times */
	dwell(0.5);		/* And wait 0.5s before continuing */

repeat(10; i) {
	move([val * i / 10.0]);


Functions can be defined anywhere in the source. They have the following format:
function name(optargs) { ... }
Any defined variables in functions are local by default unless a global variable of same name already exists. Variables can be forced to be in the local scope when declared as local before use. Explicitly declared local variables will hide the global counterpart if it exists.

The local keyword allows for multiple variables to be declared local in a comma separated list. Each local declaration may additionally have an assignment.
function blabla() {
	local foo, bar;
	local var = 123, val = 0;
	const localconstant = 999;
Variables declared constant inside a function are also entered into the local scope and will cease to exist after the function returns.

Function return

Functions may return a value using the return statement. The function will terminate immediately when a return is executed. A return without argument effectively returns an <undef> value. Trying to assigns a value from a function that did not use a return statement to exit the function results in a runtime error.
function one()
	return 1;

function nothing()

message("one(): ", one());		// Prints "one(): 1"
message("nothing(): ", nothing());	// Prints "nothing(): <undef>"

Function arguments by reference

All arguments passed to functions are passed by value by default. Passing by reference is possible when the function definition marks the appropriate parameter with an ampersand (&). Example:
function func(valval, &valref)
	valref *= 10;
	valval *= 10;

i = 1;
j = 1;
func(i, j);
message("i=", i, ", j=", j);	// Should print "i=1, j=10"
It should be noted that passing large vectorlists as values is slower than passing them by reference. If you use (very) large vectorlists with many (large) vectors, then passing them by reference may speed up your application. The definition of "large vectorlists" is somewhat arbitrary, but you should consider it when you have 50..100 vectors in a list being passed in deeply nested function calls.

Default function arguments

Function arguments may be setup with default values. All following arguments in the definition of a function must include default values once an argument has been assigned a default value. The default may be an arbitrary expression.
function defargfunc(arg, defarg1 = 123, defarg2 = [1, sin(45.0deg)])
	comment(arg, " ", defarg1, " ", defarg2);

defargfunc(1, 2, 3);	/* Prints (1 2 3) */
defargfunc(1, 2);	/* Prints (1 2 [1,0.70710678]) */
defargfunc(1);		/* Prints (1 123 [1,0.70710678]) */
Using default arguments does not check the type of the argument in the call. In other words, the type of the argument may change depending whether or not the argument was passed in the call or not. You can query the actual type using the is*() functions.

A slightly more useful example:
SAFEZ = [-, -, 10.0];
CUTZ = [-, -, -5.0];
HOME  = [0, 0];

function do_stuff(vecs)
	local v;		/* Forces v to be local */
	goto(vecs[-1]);		/* Goto the last point */
	move(CUTZ);		/* Move to cutting depth */
	foreach(vecs; v) {	/* Iterate over the corners */
	goto(SAFEZ);		/* Retract */

list  = { [0,0], [1,0], [1,1], [0,1] };

Including files

A script can include other files using the include() function. The files are searched along a search path added with one or more -I options on the command-line. Gcmc always adds the current directory to the search path as last entry.
Overengineering @ request