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.

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-14| 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 characters delimited by double quotes ("). Standard backslash escape sequences are supported, including octal and hex escapes. Embedded nul-characters are not allowed. Example:
"This is a string with \101scapes in \x44ifferent styles\n"

Units

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

Examples: 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 upon 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.

Note: gcmc allows for output in imperial units (-i option). All internal calculations are adapted to default to imperial units if the -i option is specified.

Operators

Calculations are performed with unary and binary operators with following precedence:
()parenthesis
[] id++ id--vector/vector-list index, post-increment, post-decrement
! + - ++id --idunary: logical not, plus, minus, pre-increment, pre-decrement
* / %multiplication, division, modulo
+ - +| -|binary plus, minus, plusor and minusor
<< >>shift left, shift right
> < >= <= == !=comparison
&&logical and
||logical or
?:conditional expression
= += -= +|= -|= *= /= %= <<= >>=assignment

Floating point numbers are considered equal if they are within |1e-14| of each other. The value 1e-14 is called EPSILON and is used in all comparisons.

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 take beware when using ternary operators.

Operators +| and -| work the same as + and - with the exception of how undef values are handled. See below for details.

Valid operations and result:
scalar+scalarscalar
vector+vectorvector
vectorlist+vectorvectorlist
vectorlist+vectorlistvectorlist (append)
string+scalarstring (printf)
string+vectorstring (printf)
string+vectorliststring (printf)
string+stringstring (concat)
scalar-scalarscalar
vector-vectorvector
vectorlist-vectorvectorlist
scalar*scalarscalar
scalar*vectorvector (calculated as vector * scalar)
scalar*vectorlistvectorlist (calculated as vectorlist * scalar)
vector*scalarvector
vector*vectorscalar (dot product)
vectorlist*scalarvectorlist
scalar/scalarscalar
vector/scalarvector
vectorlist/scalarvectorlist
scalar%scalarscalar
vector%scalarvector
vectorlist%scalarvectorlist
scalar<<scalarscalar
vector<<scalarvector
vectorlist<<scalarvectorlist
scalar>>scalarscalar
vector>>scalarvector
vectorlist>>scalarvectorlist

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 RHS is not evaluated if the LHS of the expression pre-determines the outcome.

Index operator [] works on both lvalue and rvalue. Index values must be scalar. 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. Vector indices greater than 9 will result in a warning. Vectorlists may be indexed to any value. 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, -, -, -, -, 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 */
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]}

Undefined values may be used in some calculations, resulting in a real value or undefined. Rules for calculating with undefined are:
undef+undefundef
undef+xundef
x+undefx
undef+|undefundef
undef+|xx (if x is scalar, else error)
x+|undefx
undef-undefundef
undef-xundef
x-undefx
undef-|undefundef
undef-|x-x (as 0-x)
x-|undefx
undef*undefundef
undef*xundef (if x is scalar, else error)
x*undefundef
undef/undefundef
undef/xundef (if x is scalar, else error)
x/undefundef
undef%undefundef
undef%xundef (if x is scalar, else error)
x%undefundef
undef<<undefundef
undef<<xundef
x<<undefx
undef>>undefundef
undef>>xundef
x>>undefx
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 enter a literal undef in the source by using the the following notation:
undefvar = [-][0];
The literal undef is actually a vector with one undefined coordinate from which you request the first entry, which happens to be undef.

Please note that the undef interpretation for +| and -| has consequences when moving vectors and vectorlists by a fixed offet. 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.

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:
[1]+[-, 1][1, -]
[1]+|[-, 1][1, 1]
[1]-[-, 1][1, -]
[1]-|[-, 1][1, -1]

Variables and statements

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
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
Example:
var123 = [1, 2, 3, 4, 5];
feedrate(125.77);
_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] */

Flow control

Program flow is controlled by standard conditional and loop control statements: 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-list type and the second an identifier (loop-variable). The identifier is assigned a copy of each vector from the vector list before executing the loop content. Example:
list = { [0, 0], [1, 0], [1, 1], [0, 1] };
foreach(list; v) {
	move(v);
}

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;
	...
}
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>"
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.

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")