G-Code Meta Compiler (GCMC)
Introduction
Everything written in this document is taken from the official site of GCMC. Both GCMC and its documentation are licensed under GPLv3. See Gcmc's homepage for details.

Gcmc is a front-end language for generating G-code, SVG and DXF for CNC mills, lathes, laser cutters and other numerical controlled machines employing G-code, SVG or DXF. The language is a context-free grammar created to overcome the archaic format of G-code programming and aims to be more readable and understandable. Gcmc makes extensive use of vector mathematics to support the 3D nature of CNC machining. An introduction document is available. The introduction goes through basic syntax and program outline and describes an example to create involute gears in under 200 lines of code (under 100 lines when excluding source code comments).

First small steps
To get started with gcmc, you need to get a feeling for the basic outline of the language.
Question: How to move your machine from one place to another?
Answer:
move ([1, 2, 3]);
The above example performs a move at the current feed-rate to coordinates X=1, Y=2 and Z=3. If you instead want to perform a rapid move, then you write:
goto([0, 0, 10]);
Which moves the machine to coordinates X=0, Y=0, Z=10 as a rapid move.

Both examples use a vector to describe the position to which the machine should move. A vector is enclosed in square brackets ('[' and ']') and contains a comma-separated list of coordinates. Both move () and goto () functions take a vector as argument to emit the command to move the machine.

Vectors may contain undefined coordinates, which instruct the move() and goto() functions to prevent the corresponding axes to move at all. For example, you may wish to move only the Z-axis to ensure the machine to go to a safe height before any other movement is performed. This can be accomplished using:
goto([-, -, 10]);	/* Move the Z-axis */
goto([0, 0]);		/* Move XY-axes */
A vector coordinate entered as '-' is an undefined entry. A vector's coordinates are interpreted by many functions as [X, Y, Z, A, B, C, U, V, W] and any coordinate not specified omits that axis in the movement output. Vector coordinates omitted at the end of a vector are implied to be undefined. I.e. [1, 2] is interpreted as [1, 2, -, -, -, -, -, -, -] when movement is concerned.

Comments in the gcmc source are entered with // or /*…*/, for comments to end-of-line and block-comments respectively.
Above examples result in following G-code snippets:
G1 X1.00000000 Y2.00000000 Z3.00000000

G0 X0.00000000 Y0.00000000 Z10.00000000

G0 Z10.00000000
G0 X0.00000000 Y0.00000000
Using a variable
Simple movement with goto() and move() become quite involved very fast. You want to be able to give symbolic names to a location and also need to calculate new positions. Variables are used to do both. A variable is a symbolic name for a value, location or a list of locations. A simple example, extending from above could look like:
SafeZ = [-, -, 10];
HomePos = [0, 0];
goto(SafeZ);	/* Retract Z */
goto(HomePos);	/* Back to home-base */
Variable can be used in calculations and simple vector math may create customized paths is simple ways. One way to create a path is to use a vectorlist type. A vector-list is a comma separated collection of vectors enclosed in curly brackets ('{' and '}'). Such list may be manipulated in different ways for the list to represent specific movement.
SafeZ   = [-, -, 10];
CutZ    = [-, -, -1];
HomePos = [0, 0];
Square  = { [0, 0], [1, 0], [1, 1], [0, 1] };
goto(Square[3]);	/* To last point */
move(CutZ);		/* Goto cutting depth */
move(Square[0]);	/* Cut the square */
move(Square[1]);
move(Square[2]);
move(Square[3]);
goto(SafeZ);		/* Retract */
goto(HomePos);		/* Back to home-base */
Each element in a vector-list can be addressed using an index starting from zero. However, there is no reason to iterate through all entries manually as it can be performed automatically using a foreach construct. Secondly, the square can be scaled and moved to any size and place using simple math.
SafeZ   = [-, -, 10.0mm];
CutZ    = [-, -, -1.0mm];
HomePos = [0.0mm, 0.0mm];
Square  = { [0, 0], [1, 0], [1, 1], [0, 1] };
Offset  = [-2.0mm, 5.0mm];
Square  = Square * 10.0in + Offset;	/* Scale and move the square */
feedrate(100mm);			/* Set feed so it can be visualized by LinuxCNC */
goto(Square[-1]);			/* To last point */
move(CutZ);				/* Goto cutting depth */
foreach(Square; v) {
	move(v);			/* Cut the square */
}
goto(SafeZ);				/* Retract */
goto(HomePos);				/* Back to home-base */
Above example scales the square by a factor 10 and moves all point of the square -2 in X and +5 in Y, resulting in {[-2, 5], [8, 5], [8, 15], [-2, 15]}.

Another small change can be seen in goto(Square[-1]). The index changes from '3' to '-1'. Indices can be both positive and negative. A positive index starts counting from the start, where 0 (zero) is the first entry. A negative index starts at -1, which indicates the last entry, -2 the second last entry, etc.. Using negative indices is a simple way to get to the last entry/entries of a list if the size of the list is unknown beforehand.

The foreach construct takes the list to iterate over as the first parameter and a second parameter denotes the variable name which will receive the individual vectors. The feedrate() function was added to set the feed-rate of the machine (F word in G-code). LinuxCNC reports an error and refuses to visualize a program if no feed-rate is set (or is zero).
Using units and numbers
CNC machines work in a real world where measures are important. The machines are fed by numbers representing units millimeters or inches. Most machines can be instructed to interpret bare numbers as either, which can lead to a lot of work if designs have origins in both metric and imperial units. Using both metric and imperial units can also be prone to inadvertent errors due to manual conversions.

Gcmc supports the use of units as part of the language and will automatically do all conversions. Gcmc can generate output in either metric or imperial units and, if used consistently, will generate the exact same result in terms of absolute size when either is selected.

Units can be attached to any number in gcmc by appending 'mm', 'in', 'deg' or 'rad' to the number, where deg/rad are angular units degrees and radians respectively. One special form is supported for anyone in PCB design, who will probably know the 'mil' (1/1000 of an inch), which is automatically converted into inches.
metric   = 100mm;
imperial = 5in;
pcbmils  = 400mil;	/* converted to 0.4in */
degrees  = 30deg;
radians  = 3.14159265359rad;
Any number entered can be of integer or floating point type. Any number containing a decimal point or an exponent is a floating point number. Gcmc preserves the type of number as far as possible, meaning that integers stay integer and floating point stay floating point when ever possible. Floating point numbers are generally never converted into integers, but integers may be promoted to floating point numbers. Beware: many real-world positional calculations require floating point precision. It is often a good idea to ensure that coordinates are floating point and have units associated.

Conversions primarily occur when mixed type calculations are performed and when units need to be converted.
metric   = 100mm;
imperial = 5in;
mi = metric + imperial;	/* mi becomes metric float 227.0mm */
im = imperial + metric;	/* im becomes imperial float 8.93700787402in */

i1 = 10;
i2 = 4;
divi = i1 / i2;		/* divi becomes integer 2 */

fl = 4.0;
divf = i1 / fl;		/* divf becomes float 2.5 */
Numbers with units associated are converted to the unit on the left-hand-side of the calculation. Unit conversion always implies conversion to floating point. If either of the numbers has no units associated, then the one with units will be used and conversion to floating point will occur only if any is floating point to start with. A more detailed description is available at the unit syntax description with all combinations specified.

Revisiting the example from above with proper units attached may look like:
SafeZ   = [-, -, 10.0mm];
CutZ    = [-, -, -1.0mm];
HomePos = [0.0mm, 0.0mm];
Square  = { [0, 0], [1, 0], [1, 1], [0, 1] };
Offset  = [-2.0mm, 5.0mm];
Square  = Square * 10.0in + Offset;	/* Scale and move the square */
feedrate(100mm);			/* Set feed so it can be visualized by LinuxCNC */
goto(Square[-1]);			/* To last point */
move(CutZ);				/* Goto cutting depth */
foreach(Square; v) {
	move(v);			/* Cut the square */
}
goto(SafeZ);				/* Retract */
goto(HomePos);				/* Back to home-base */
Please note that the definition of the square has no units attached. The square is an abstract form which receives the actual size and position by 10 inch scaling and metric offsetting. Compiling the example with gcmc gives following results:
F100.00000000
G0 X-2.00000000 Y259.00000000
G1 Z-1.00000000
G1 X-2.00000000 Y5.00000000
G1 X252.00000000 Y5.00000000
G1 X252.00000000 Y259.00000000
G1 X-2.00000000 Y259.00000000
G0 Z10.00000000
G0 X0.00000000 Y0.00000000
User functions
Repeating patterns often occur when machining a particular part. It is often desirable to describe the repeating patterns as a function which can be called over and over again.
SafeZ   = [-, -, 10.0mm];
CutZ    = [-, -, -1.0mm];
HomePos = [0.0mm, 0.0mm];
Square  = { [0, 0], [1, 0], [1, 1], [0, 1] };

function cut_the_path(path, offset)
{
	path += offset;		/* Move the path to the actual position */
	goto(path[-1]);		/* To last point */
	move(CutZ);		/* Goto cutting depth */
	foreach(path; v) {
		move(v);	/* Cut the path */
	}
	goto(SafeZ);		/* Retract */
}

feedrate(100mm);				/* Set feed so it can be visualized by LinuxCNC */
goto(SafeZ);					/* Initial to safe Z retraction point*/
cut_the_path(Square * 1in, [10.0mm, 5.0mm]);	/* First square */
cut_the_path(Square * 2in, [15.0mm, 15.0mm]);	/* Second square */
cut_the_path(Square * 3in, [20.0mm, 25.0mm]);	/* Third square */
cut_the_path(Square * 4in, [25.0mm, 35.0mm]);	/* Fourth square */
cut_the_path(Square * 5in, [30.0mm, 45.0mm]);	/* Fifth square */
goto(HomePos);					/* Back to home-base */
Functions can take as many parameters as required and are most often passed as values (see functions syntax reference for more details). Above example can be further abstracted by using the repeat construct. The path is cut five times with regular intervals, which can be expressed in a mathematical way:
SafeZ   = [-, -, 10.0mm];
CutZ    = [-, -, -1.0mm];
HomePos = [0.0mm, 0.0mm];
Square  = { [0, 0], [1, 0], [1, 1], [0, 1] };

function cut_the_path(path, offset)
{
	path += offset;		/* Move the path to the actual position */
	goto(path[-1]);	/* To last point */
	move(CutZ);		/* Goto cutting depth */
	foreach(path; v) {
		move(v);	/* Cut the path */
	}
	goto(SafeZ);		/* Retract */
}

feedrate(100mm);		/* Set feed so it can be visualized by LinuxCNC */
goto(SafeZ);			/* Initial to safe Z retraction point*/
repeat(5; i) {
	cut_the_path(Square * (1.0in + i), [10.0mm, 5.0mm] + [5.0mm, 10.0mm] * i);
}
goto(HomePos);			/* Back to home-base */
The repeat construct sets the variable 'i' to the sequence 1, 2, 3, 4, 5 and is used to calculate the scaling factor by addition and the offset by scaled addition. It should be noted that the scaling "1.0in + i" adds inches and a number without units. Gcmc handles such case by defaulting the units to the side with units associated (see unit description for rules).
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.
Types
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.

Example:

"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.
Units
All scalar types can have units associated. Available units are:
Examples:
  • 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):
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:
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.
Operators
Calculations are performed with unary and binary operators with following precedence:
Valid operations and result:
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:
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 << 2			→	4
6 >> 1			→	3
[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];
feedrate(125.77mm);
_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 */
Constants
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:
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.
if()/elif()/else
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");
	return;
}

if(value >= 43) {
	/* bla */
} elif(value < 0) {
	/* Negative bla */
} elif(value == 5) {
	/* the right bla */
} else {
	/* None of the above bla */
foreach()
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) {
	move(v);
}
foreach([1, 7, 3, 6, 4, 3, 1, 9, 0, 6, 4]; i) {
	complex_sequenced_func(i);
}
for()
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++) {
	do_something(i);
}
while()
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);
}
do/while()
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);
repeat()
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
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()
{
	return;
}

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 */
		move(v);
	}
	goto(SAFEZ);		/* Retract */
}

list  = { [0,0], [1,0], [1,1], [0,1] };
do_stuff(list);
goto(HOME);
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.
Example:
include("other-file.gcmc")