gcmc - G-Code Meta CompilerSyntax description
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:
Units
All scalar types can have units associated. Available units are:
Examples:
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:
Distance unit conversion at output: When G-code is output, then all units are converted to millimeters or inches for axes XYZ and UVW depending the -i option (without -i:G21/mm; with -i:G20/in). Values on axes XYZ and UVW with no units associated are treated as if they were millimeters or inches as appropriate for the respective axis depending the -i option. Angular unit conversion at output: Axes ABC will always output values in degrees, as per G-code standard. However, the values for the ABC axes without units associated are interpreted as radians and are converted to degrees. The behaviour has been changed in version 1.9.0, where all angular arguments now default to radians for consistency if no units were associated. You must add the --degrees option to gcmc if you want the old behaviour. The --degrees option only has influence on the angular units for move() and goto() vectors on the ABC axes if no units were associated to the values. 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. Also conversions between degrees and radians may cause surprises, if units are not explicitly 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 // trueThe 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:
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:
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:
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.
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:
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.60000000mmRunning 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.07874016inPortable 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 modeYou 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:
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 */ ConstantsVariables 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:
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 returnFunctions 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 referenceAll 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 argumentsFunction 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")
|
Overengineering @ request | Prutsen & Pielen since 1982 |