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:

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.

Operators

Calculations are performed with unary and binary operators with following precedence:

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.

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.

Undef handling

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 assign a literal undef to a variable by using the following notations:

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

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.

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.

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:

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:

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:

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:

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:

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:

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.

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:

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.

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:

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:

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:

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.

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.

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.

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.

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: