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:
mm | Millimeters |
in | Inch (25.4mm) |
mil | Mil = 1/1000 inch (0.0254mm); always converted to inch |
deg | Degrees, 360° full circle |
rad | Radians, 2π full circle |
| No units (none) |
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):
<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 |
vector/vector-list index, post-increment, post-decrement |
unary: 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 | + | scalar | → | scalar |
vector | + | vector | → | vector |
vectorlist | + | vector | → | vectorlist |
vectorlist | + | vectorlist | → | vectorlist (append) |
string | + | scalar | → | string (printf) |
string | + | vector | → | string (printf) |
string | + | vectorlist | → | string (printf) |
string | + | string | → | string (concat) |
scalar | - | scalar | → | scalar |
vector | - | vector | → | vector |
vectorlist | - | vector | → | vectorlist |
scalar | * | scalar | → | scalar |
scalar | * | vector | → | vector (calculated as vector * scalar) |
scalar | * | vectorlist | → | vectorlist (calculated as vectorlist * scalar) |
vector | * | scalar | → | vector |
vector | * | vector | → | scalar (dot product) |
vectorlist | * | scalar | → | vectorlist |
scalar | / | scalar | → | scalar |
vector | / | scalar | → | vector |
vectorlist | / | scalar | → | vectorlist |
scalar | % | scalar | → | scalar |
vector | % | scalar | → | vector |
vectorlist | % | scalar | → | vectorlist |
scalar | << | scalar | → | scalar |
vector | << | scalar | → | vector |
vectorlist | << | scalar | → | vectorlist |
scalar | >> | scalar | → | scalar |
vector | >> | scalar | → | vector |
vectorlist | >> | scalar | → | vectorlist |
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 << 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]} |
Undefined values may be used in some calculations, resulting in a real value or
undefined. Rules for calculating with undefined are:
undef | + | undef | → | undef |
undef | + | x | → | undef |
x | + | undef | → | x |
undef | +| | undef | → | undef |
undef | +| | x | → | x (if x is scalar, else error) |
x | +| | undef | → | x |
undef | - | undef | → | undef |
undef | - | x | → | undef |
x | - | undef | → | x |
undef | -| | undef | → | undef |
undef | -| | x | → | -x (as 0-x) |
x | -| | undef | → | x |
undef | * | undef | → | undef |
undef | * | x | → | undef (if x is scalar, else error) |
x | * | undef | → | undef |
undef | / | undef | → | undef |
undef | / | x | → | undef (if x is scalar, else error) |
x | / | undef | → | undef |
undef | % | undef | → | undef |
undef | % | x | → | undef (if x is scalar, else error) |
x | % | undef | → | undef |
undef | << | undef | → | undef |
undef | << | x | → | undef |
x | << | undef | → | x |
undef | >> | undef | → | undef |
undef | >> | x | → | undef |
x | >> | undef | → | x |
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:
- 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-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")