diff --git a/FluidNC/data/index.html.gz b/FluidNC/data/index.html.gz
index 841854706..b7c05f618 100644
Binary files a/FluidNC/data/index.html.gz and b/FluidNC/data/index.html.gz differ
diff --git a/FluidNC/src/Channel.h b/FluidNC/src/Channel.h
index 2e96a540d..2eae69485 100644
--- a/FluidNC/src/Channel.h
+++ b/FluidNC/src/Channel.h
@@ -193,6 +193,8 @@ class Channel : public Stream {
size_t lineNumber() { return _line_number; }
- virtual void save() {}
- virtual void restore() {}
+ virtual void save() {}
+ virtual void restore() {}
+ virtual size_t position() { return 0; }
+ virtual void set_position(size_t pos) {}
};
diff --git a/FluidNC/src/Error.cpp b/FluidNC/src/Error.cpp
index 6d2d233f3..9f5d19bfb 100644
--- a/FluidNC/src/Error.cpp
+++ b/FluidNC/src/Error.cpp
@@ -83,4 +83,9 @@ const std::map ErrorNames = {
{ Error::ExpressionUnknownOp, "Expression Unknown Operator" },
{ Error::ExpressionArgumentOutOfRange, "Expression Argument Out of Range" },
{ Error::ExpressionSyntaxError, "Expression Syntax Error" },
+ { Error::FlowControlSyntaxError, "Flow Control Syntax Error" },
+ { Error::FlowControlNotExecutingMacro, "Flow Control Not Executing Macro" },
+ { Error::FlowControlOutOfMemory, "Flow Control Out of Memory" },
+ { Error::FlowControlStackOverflow, "Flow Control Stack Overflow" },
+ { Error::ParameterAssignmentFailed, "Parameter Assignment Failed" },
};
diff --git a/FluidNC/src/Error.h b/FluidNC/src/Error.h
index a03a388f5..153fd274d 100644
--- a/FluidNC/src/Error.h
+++ b/FluidNC/src/Error.h
@@ -88,6 +88,11 @@ enum class Error : uint8_t {
ExpressionUnknownOp = 173,
ExpressionArgumentOutOfRange = 174,
ExpressionSyntaxError = 175,
+ FlowControlSyntaxError = 176,
+ FlowControlNotExecutingMacro = 177,
+ FlowControlOutOfMemory = 178,
+ FlowControlStackOverflow = 179,
+ ParameterAssignmentFailed = 180,
};
const char* errorString(Error errorNumber);
diff --git a/FluidNC/src/Expression.cpp b/FluidNC/src/Expression.cpp
index 2d3ab0132..814ae1edd 100644
--- a/FluidNC/src/Expression.cpp
+++ b/FluidNC/src/Expression.cpp
@@ -1,3 +1,4 @@
+// Expression.cpp - derived from
// ngc_expr.c - derived from:
/********************************************************************
@@ -39,43 +40,43 @@
#define MAX_STACK 7
typedef enum {
- NGCBinaryOp_NoOp = 0,
- NGCBinaryOp_DividedBy,
- NGCBinaryOp_Modulo,
- NGCBinaryOp_Power,
- NGCBinaryOp_Times,
- NGCBinaryOp_Binary2 = NGCBinaryOp_Times,
- NGCBinaryOp_And2,
- NGCBinaryOp_ExclusiveOR,
- NGCBinaryOp_Minus,
- NGCBinaryOp_NotExclusiveOR,
- NGCBinaryOp_Plus,
- NGCBinaryOp_RightBracket,
- NGCBinaryOp_RelationalFirst,
- NGCBinaryOp_LT = NGCBinaryOp_RelationalFirst,
- NGCBinaryOp_EQ,
- NGCBinaryOp_NE,
- NGCBinaryOp_LE,
- NGCBinaryOp_GE,
- NGCBinaryOp_GT,
- NGCBinaryOp_RelationalLast = NGCBinaryOp_GT,
+ Binary_NoOp = 0,
+ Binary_DividedBy,
+ Binary_Modulo,
+ Binary_Power,
+ Binary_Times,
+ Binary_Binary2 = Binary_Times,
+ Binary_And2,
+ Binary_ExclusiveOR,
+ Binary_Minus,
+ Binary_NotExclusiveOR,
+ Binary_Plus,
+ Binary_RightBracket,
+ Binary_RelationalFirst,
+ Binary_LT = Binary_RelationalFirst,
+ Binary_EQ,
+ Binary_NE,
+ Binary_LE,
+ Binary_GE,
+ Binary_GT,
+ Binary_RelationalLast = Binary_GT,
} ngc_binary_op_t;
typedef enum {
- NGCUnaryOp_ABS = 1,
- NGCUnaryOp_ACOS,
- NGCUnaryOp_ASIN,
- NGCUnaryOp_ATAN,
- NGCUnaryOp_COS,
- NGCUnaryOp_EXP,
- NGCUnaryOp_FIX,
- NGCUnaryOp_FUP,
- NGCUnaryOp_LN,
- NGCUnaryOp_Round,
- NGCUnaryOp_SIN,
- NGCUnaryOp_SQRT,
- NGCUnaryOp_TAN,
- NGCUnaryOp_Exists, // Not implemented
+ Unary_ABS = 1,
+ Unary_ACOS,
+ Unary_ASIN,
+ Unary_ATAN,
+ Unary_COS,
+ Unary_EXP,
+ Unary_FIX,
+ Unary_FUP,
+ Unary_LN,
+ Unary_Round,
+ Unary_SIN,
+ Unary_SQRT,
+ Unary_TAN,
+ Unary_Exists, // Not implemented
} ngc_unary_op_t;
static void report_param_error(Error err) {
@@ -100,27 +101,27 @@ static Error execute_binary1(float& lhs, ngc_binary_op_t operation, const float&
Error status = Error::Ok;
switch (operation) {
- case NGCBinaryOp_DividedBy:
+ case Binary_DividedBy:
if (rhs == 0.0f || rhs == -0.0f)
status = Error::ExpressionDivideByZero; // Attempt to divide by zero
else
lhs = lhs / rhs;
break;
- case NGCBinaryOp_Modulo: // always calculates a positive answer
+ case Binary_Modulo: // always calculates a positive answer
lhs = fmodf(lhs, rhs);
if (lhs < 0.0f)
lhs = lhs + fabsf(rhs);
break;
- case NGCBinaryOp_Power:
+ case Binary_Power:
if (lhs < 0.0f && floorf(rhs) != rhs)
status = Error::ExpressionInvalidArgument; // Attempt to raise negative value to non-integer power
else
lhs = powf(lhs, rhs);
break;
- case NGCBinaryOp_Times:
+ case Binary_Times:
lhs = lhs * rhs;
break;
@@ -147,51 +148,51 @@ Any non-zero input value is taken as meaning true, and only 0.0 means false.
*/
static Error execute_binary2(float& lhs, ngc_binary_op_t operation, const float& rhs) {
switch (operation) {
- case NGCBinaryOp_And2:
+ case Binary_And2:
lhs = ((lhs == 0.0f) || (rhs == 0.0f)) ? 0.0f : 1.0f;
break;
- case NGCBinaryOp_ExclusiveOR:
+ case Binary_ExclusiveOR:
lhs = (((lhs == 0.0f) && (rhs != 0.0f)) || ((lhs != 0.0f) && (rhs == 0.0f))) ? 1.0f : 0.0f;
break;
- case NGCBinaryOp_Minus:
+ case Binary_Minus:
lhs = (lhs - rhs);
break;
- case NGCBinaryOp_NotExclusiveOR:
+ case Binary_NotExclusiveOR:
lhs = ((lhs != 0.0f) || (rhs != 0.0f)) ? 1.0f : 0.0f;
break;
- case NGCBinaryOp_Plus:
+ case Binary_Plus:
lhs = (lhs + rhs);
break;
- case NGCBinaryOp_LT:
+ case Binary_LT:
lhs = (lhs < rhs) ? 1.0f : 0.0f;
break;
- case NGCBinaryOp_EQ: {
+ case Binary_EQ: {
float diff = lhs - rhs;
diff = (diff < 0.0f) ? -diff : diff;
lhs = (diff < TOLERANCE_EQUAL) ? 1.0f : 0.0f;
} break;
- case NGCBinaryOp_NE: {
+ case Binary_NE: {
float diff = lhs - rhs;
diff = (diff < 0.0f) ? -diff : diff;
lhs = (diff >= TOLERANCE_EQUAL) ? 1.0f : 0.0f;
} break;
- case NGCBinaryOp_LE:
+ case Binary_LE:
lhs = (lhs <= rhs) ? 1.0f : 0.0f;
break;
- case NGCBinaryOp_GE:
+ case Binary_GE:
lhs = (lhs >= rhs) ? 1.0f : 0.0f;
break;
- case NGCBinaryOp_GT:
+ case Binary_GT:
lhs = (lhs > rhs) ? 1.0f : 0.0f;
break;
@@ -212,7 +213,7 @@ This just calls either execute_binary1 or execute_binary2.
\returns #Error::Ok enum value if processed without error, appropriate \ref Error enum value if not.
*/
static Error execute_binary(float& lhs, ngc_binary_op_t operation, const float& rhs) {
- if (operation <= NGCBinaryOp_Binary2)
+ if (operation <= Binary_Binary2)
return execute_binary1(lhs, operation, rhs);
return execute_binary2(lhs, operation, rhs);
@@ -230,68 +231,68 @@ static Error execute_unary(float& operand, ngc_unary_op_t operation) {
Error status = Error::Ok;
switch (operation) {
- case NGCUnaryOp_ABS:
+ case Unary_ABS:
if (operand < 0.0f)
operand = (-1.0f * operand);
break;
- case NGCUnaryOp_ACOS:
+ case Unary_ACOS:
if (operand < -1.0f || operand > 1.0f)
status = Error::ExpressionArgumentOutOfRange; // Argument to ACOS out of range
else
operand = acosf(operand) * DEGRAD;
break;
- case NGCUnaryOp_ASIN:
+ case Unary_ASIN:
if (operand < -1.0f || operand > 1.0f)
status = Error::ExpressionArgumentOutOfRange; // Argument to ASIN out of range
else
operand = asinf(operand) * DEGRAD;
break;
- case NGCUnaryOp_COS:
+ case Unary_COS:
operand = cosf(operand * RADDEG);
break;
- case NGCUnaryOp_Exists:
+ case Unary_Exists:
// do nothing here, result for the EXISTS function is set by read_unary()
break;
- case NGCUnaryOp_EXP:
+ case Unary_EXP:
operand = expf(operand);
break;
- case NGCUnaryOp_FIX:
+ case Unary_FIX:
operand = floorf(operand);
break;
- case NGCUnaryOp_FUP:
+ case Unary_FUP:
operand = ceilf(operand);
break;
- case NGCUnaryOp_LN:
+ case Unary_LN:
if (operand <= 0.0f)
status = Error::ExpressionArgumentOutOfRange; // Argument to LN out of range
else
operand = logf(operand);
break;
- case NGCUnaryOp_Round:
+ case Unary_Round:
operand = (float)((int)(operand + ((operand < 0.0f) ? -0.5f : 0.5f)));
break;
- case NGCUnaryOp_SIN:
+ case Unary_SIN:
operand = sinf(operand * RADDEG);
break;
- case NGCUnaryOp_SQRT:
+ case Unary_SQRT:
if (operand < 0.0f)
status = Error::ExpressionArgumentOutOfRange; // Negative argument to SQRT
else
operand = sqrtf(operand);
break;
- case NGCUnaryOp_TAN:
+ case Unary_TAN:
operand = tanf(operand * RADDEG);
break;
@@ -309,33 +310,33 @@ static Error execute_unary(float& operand, ngc_unary_op_t operation) {
*/
static uint_fast8_t precedence(ngc_binary_op_t op) {
switch (op) {
- case NGCBinaryOp_RightBracket:
+ case Binary_RightBracket:
return 1;
- case NGCBinaryOp_And2:
- case NGCBinaryOp_ExclusiveOR:
- case NGCBinaryOp_NotExclusiveOR:
+ case Binary_And2:
+ case Binary_ExclusiveOR:
+ case Binary_NotExclusiveOR:
return 2;
- case NGCBinaryOp_LT:
- case NGCBinaryOp_EQ:
- case NGCBinaryOp_NE:
- case NGCBinaryOp_LE:
- case NGCBinaryOp_GE:
- case NGCBinaryOp_GT:
+ case Binary_LT:
+ case Binary_EQ:
+ case Binary_NE:
+ case Binary_LE:
+ case Binary_GE:
+ case Binary_GT:
return 3;
- case NGCBinaryOp_Minus:
- case NGCBinaryOp_Plus:
+ case Binary_Minus:
+ case Binary_Plus:
return 4;
- case NGCBinaryOp_NoOp:
- case NGCBinaryOp_DividedBy:
- case NGCBinaryOp_Modulo:
- case NGCBinaryOp_Times:
+ case Binary_NoOp:
+ case Binary_DividedBy:
+ case Binary_Modulo:
+ case Binary_Times:
return 5;
- case NGCBinaryOp_Power:
+ case Binary_Power:
return 6;
default:
@@ -345,6 +346,42 @@ static uint_fast8_t precedence(ngc_binary_op_t op) {
return 0; // should never happen
}
+#if 0
+// For possible later code simplification
+std::map> binary_ops = {
+ { "+", Binary_Plus },
+ { "-", Binary_Minus },
+ { "/", Binary_DividedBy },
+ { "*", Binary_Times },
+ { "**", Binary_Power },
+ { "]", Binary_RightBracket },
+ { "AND", Binary_And2 },
+ { "MOD", Binary_Mod },
+ { "RR", Binary_NotExclusiveOr },
+ { "XOR", Binary_ExclusiveOr },
+ { "EQ", Binary_EQ },
+ { "NE", Binary_NE },
+ { "GE", Binary_GE },
+ { "GT", Binary_GT },
+};
+std::map> unary_ops = {
+ { "ABS", Unary_ABS },
+ { "ACOS", Unary_ACOS },
+ { "ASIN", Unary_ASIN },
+ { "ATAN", Unary_ATAN },
+ { "COS", Unary_COS },
+ { "EXP", Unary_EXP },
+ { "EXISTS", Unary_Exists },
+ { "FIX", Unary_FIX },
+ { "FUP", Unary_FUP },
+ { "LN", Unary_LN },
+ { "ROUND", Unary_ROUND },
+ { "SIN", Unary_SIN },
+ { "SQRT", Unary_SQRT },
+ { "TAN", Unary_TAN },
+};
+#endif
+
/*! \brief Reads a binary operation out of the line
starting at the index given by the pos offset. If a valid one is found, the
value of operation is set to the symbolic value for that operation.
@@ -354,104 +391,104 @@ value of operation is set to the symbolic value for that operation.
\param operation pointer to \ref ngc_binary_op_t enum value.
\returns #Error::Ok enum value if processed without error, appropriate \ref Error enum value if not.
*/
-static Error read_operation(const char* line, size_t* pos, ngc_binary_op_t* operation) {
- char c = line[*pos];
+static Error read_operation(const char* line, size_t& pos, ngc_binary_op_t& operation) {
+ char c = line[pos];
Error status = Error::Ok;
- (*pos)++;
+ pos++;
switch (c) {
case '+':
- *operation = NGCBinaryOp_Plus;
+ operation = Binary_Plus;
break;
case '-':
- *operation = NGCBinaryOp_Minus;
+ operation = Binary_Minus;
break;
case '/':
- *operation = NGCBinaryOp_DividedBy;
+ operation = Binary_DividedBy;
break;
case '*':
- if (line[*pos] == '*') {
- *operation = NGCBinaryOp_Power;
- (*pos)++;
+ if (line[pos] == '*') {
+ operation = Binary_Power;
+ pos++;
} else
- *operation = NGCBinaryOp_Times;
+ operation = Binary_Times;
break;
case ']':
- *operation = NGCBinaryOp_RightBracket;
+ operation = Binary_RightBracket;
break;
case 'A':
- if (!strncmp(line + *pos, "ND", 2)) {
- *operation = NGCBinaryOp_And2;
- *pos += 2;
+ if (!strncmp(line + pos, "ND", 2)) {
+ operation = Binary_And2;
+ pos += 2;
} else
status = Error::ExpressionUnknownOp; // Unknown operation name starting with A
break;
case 'M':
- if (!strncmp(line + *pos, "OD", 2)) {
- *operation = NGCBinaryOp_Modulo;
- *pos += 2;
+ if (!strncmp(line + pos, "OD", 2)) {
+ operation = Binary_Modulo;
+ pos += 2;
} else
status = Error::ExpressionUnknownOp; // Unknown operation name starting with M
break;
case 'R':
- if (line[*pos] == 'R') {
- *operation = NGCBinaryOp_NotExclusiveOR;
- (*pos)++;
+ if (line[pos] == 'R') {
+ operation = Binary_NotExclusiveOR;
+ pos++;
} else
status = Error::ExpressionUnknownOp; // Unknown operation name starting with R
break;
case 'X':
- if (!strncmp(line + *pos, "OR", 2)) {
- *operation = NGCBinaryOp_ExclusiveOR;
- *pos += 2;
+ if (!strncmp(line + pos, "OR", 2)) {
+ operation = Binary_ExclusiveOR;
+ pos += 2;
} else
status = Error::ExpressionUnknownOp; // Unknown operation name starting with X
break;
/* relational operators */
case 'E':
- if (line[*pos] == 'Q') {
- *operation = NGCBinaryOp_EQ;
- (*pos)++;
+ if (line[pos] == 'Q') {
+ operation = Binary_EQ;
+ pos++;
} else
status = Error::ExpressionUnknownOp; // Unknown operation name starting with E
break;
case 'N':
- if (line[*pos] == 'E') {
- *operation = NGCBinaryOp_NE;
- (*pos)++;
+ if (line[pos] == 'E') {
+ operation = Binary_NE;
+ pos++;
} else
status = Error::ExpressionUnknownOp; // Unknown operation name starting with N
break;
case 'G':
- if (line[*pos] == 'E') {
- *operation = NGCBinaryOp_GE;
- (*pos)++;
- } else if (line[*pos] == 'T') {
- *operation = NGCBinaryOp_GT;
- (*pos)++;
+ if (line[pos] == 'E') {
+ operation = Binary_GE;
+ pos++;
+ } else if (line[pos] == 'T') {
+ operation = Binary_GT;
+ pos++;
} else
status = Error::ExpressionUnknownOp; // Unknown operation name starting with G
break;
case 'L':
- if (line[*pos] == 'E') {
- *operation = NGCBinaryOp_LE;
- (*pos)++;
- } else if (line[*pos] == 'T') {
- *operation = NGCBinaryOp_LT;
- (*pos)++;
+ if (line[pos] == 'E') {
+ operation = Binary_LE;
+ pos++;
+ } else if (line[pos] == 'T') {
+ operation = Binary_LT;
+ pos++;
} else
status = Error::ExpressionUnknownOp; // Unknown operation name starting with L
break;
@@ -475,91 +512,91 @@ value of operation is set to the symbolic value for that operation.
\param operation pointer to \ref ngc_unary_op_t enum value.
\returns #Error::Ok enum value if processed without error, appropriate \ref Error enum value if not.
*/
-static Error read_operation_unary(const char* line, size_t* pos, ngc_unary_op_t* operation) {
- char c = line[*pos];
+static Error read_operation_unary(const char* line, size_t& pos, ngc_unary_op_t& operation) {
+ char c = line[pos];
Error status = Error::Ok;
- (*pos)++;
+ pos++;
switch (c) {
case 'A':
- if (!strncmp(line + *pos, "BS", 2)) {
- *operation = NGCUnaryOp_ABS;
- *pos += 2;
- } else if (!strncmp(line + *pos, "COS", 3)) {
- *operation = NGCUnaryOp_ACOS;
- *pos += 3;
- } else if (!strncmp(line + *pos, "SIN", 3)) {
- *operation = NGCUnaryOp_ASIN;
- *pos += 3;
- } else if (!strncmp(line + *pos, "TAN", 3)) {
- *operation = NGCUnaryOp_ATAN;
- *pos += 3;
+ if (!strncmp(line + pos, "BS", 2)) {
+ operation = Unary_ABS;
+ pos += 2;
+ } else if (!strncmp(line + pos, "COS", 3)) {
+ operation = Unary_ACOS;
+ pos += 3;
+ } else if (!strncmp(line + pos, "SIN", 3)) {
+ operation = Unary_ASIN;
+ pos += 3;
+ } else if (!strncmp(line + pos, "TAN", 3)) {
+ operation = Unary_ATAN;
+ pos += 3;
} else
status = Error::ExpressionUnknownOp;
break;
case 'C':
- if (!strncmp(line + *pos, "OS", 2)) {
- *operation = NGCUnaryOp_COS;
- *pos += 2;
+ if (!strncmp(line + pos, "OS", 2)) {
+ operation = Unary_COS;
+ pos += 2;
} else
status = Error::ExpressionUnknownOp;
break;
case 'E':
- if (!strncmp(line + *pos, "XP", 2)) {
- *operation = NGCUnaryOp_EXP;
- *pos += 2;
- } else if (!strncmp(line + *pos, "XISTS", 5)) {
- *operation = NGCUnaryOp_Exists;
- *pos += 5;
+ if (!strncmp(line + pos, "XP", 2)) {
+ operation = Unary_EXP;
+ pos += 2;
+ } else if (!strncmp(line + pos, "XISTS", 5)) {
+ operation = Unary_Exists;
+ pos += 5;
} else
status = Error::ExpressionUnknownOp;
break;
case 'F':
- if (!strncmp(line + *pos, "IX", 2)) {
- *operation = NGCUnaryOp_FIX;
- *pos += 2;
- } else if (!strncmp(line + *pos, "UP", 2)) {
- *operation = NGCUnaryOp_FUP;
- *pos += 2;
+ if (!strncmp(line + pos, "IX", 2)) {
+ operation = Unary_FIX;
+ pos += 2;
+ } else if (!strncmp(line + pos, "UP", 2)) {
+ operation = Unary_FUP;
+ pos += 2;
} else
status = Error::ExpressionUnknownOp;
break;
case 'L':
- if (line[*pos] == 'N') {
- *operation = NGCUnaryOp_LN;
- (*pos)++;
+ if (line[pos] == 'N') {
+ operation = Unary_LN;
+ pos++;
} else
status = Error::ExpressionUnknownOp;
break;
case 'R':
- if (!strncmp(line + *pos, "OUND", 4)) {
- *operation = NGCUnaryOp_Round;
- *pos += 4;
+ if (!strncmp(line + pos, "OUND", 4)) {
+ operation = Unary_Round;
+ pos += 4;
} else
status = Error::ExpressionUnknownOp;
break;
case 'S':
- if (!strncmp(line + *pos, "IN", 2)) {
- *operation = NGCUnaryOp_SIN;
- *pos += 2;
- } else if (!strncmp((line + *pos), "QRT", 3)) {
- *operation = NGCUnaryOp_SQRT;
- *pos += 3;
+ if (!strncmp(line + pos, "IN", 2)) {
+ operation = Unary_SIN;
+ pos += 2;
+ } else if (!strncmp((line + pos), "QRT", 3)) {
+ operation = Unary_SQRT;
+ pos += 3;
} else
status = Error::ExpressionUnknownOp;
break;
case 'T':
- if (!strncmp(line + *pos, "AN", 2)) {
- *operation = NGCUnaryOp_TAN;
- *pos += 2;
+ if (!strncmp(line + pos, "AN", 2)) {
+ operation = Unary_TAN;
+ pos += 2;
} else
status = Error::ExpressionUnknownOp;
break;
@@ -580,15 +617,15 @@ of the ATAN operation applied to the two arguments.
\param value pointer to float where result is to be stored.
\returns #Error::Ok enum value if processed without error, appropriate \ref Error enum value if not.
*/
-static Error read_atan(const char* line, size_t* pos, float& value) {
+static Error read_atan(const char* line, size_t& pos, float& value) {
float argument2;
- if (line[*pos] != '/')
+ if (line[pos] != '/')
return Error::ExpressionSyntaxError; // Slash missing after first ATAN argument
- (*pos)++;
+ pos++;
- if (line[*pos] != '[')
+ if (line[pos] != '[')
return Error::ExpressionSyntaxError; // Left bracket missing after slash with ATAN;
Error status;
@@ -609,35 +646,35 @@ handled specially because it is followed by two arguments.
\returns #Error::Ok enum value if processed without error, appropriate \ref Error enum value if not.
*/
// cppcheck-suppress unusedFunction
-Error read_unary(const char* line, size_t* pos, float& value) {
+Error read_unary(const char* line, size_t& pos, float& value) {
ngc_unary_op_t operation;
Error status;
- if ((status = read_operation_unary(line, pos, &operation)) != Error::Ok) {
+ if ((status = read_operation_unary(line, pos, operation)) != Error::Ok) {
return status;
}
- if (line[*pos] != '[') {
+ if (line[pos] != '[') {
return Error::ExpressionSyntaxError; // Left bracket missing after unary operation name
}
- if (operation == NGCUnaryOp_Exists) {
- ++*pos;
+ if (operation == Unary_Exists) {
+ ++pos;
std::string arg;
char c;
- while ((c = line[*pos]) && c != ']') {
- ++*pos;
+ while ((c = line[pos]) && c != ']') {
+ ++pos;
arg += c;
}
if (!c) {
return Error::ExpressionSyntaxError;
}
- ++*pos;
+ ++pos;
value = named_param_exists(arg) ? 1.0 : 0.0;
return Error::Ok;
}
if ((status = expression(line, pos, value)) != Error::Ok) {
return status;
}
- if (operation == NGCUnaryOp_ATAN) {
+ if (operation == Unary_ATAN) {
return read_atan(line, pos, value);
}
return execute_unary(value, operation);
@@ -650,29 +687,29 @@ Error read_unary(const char* line, size_t* pos, float& value) {
\param value pointer to float where result is to be stored.
\returns #Error::Ok enum value if evaluated without error, appropriate \ref Error enum value if not.
*/
-Error expression(const char* line, size_t* pos, float& value) {
+Error expression(const char* line, size_t& pos, float& value) {
float values[MAX_STACK];
ngc_binary_op_t operators[MAX_STACK];
uint_fast8_t stack_index = 1;
- if (line[*pos] != '[')
+ if (line[pos] != '[')
return Error::GcodeUnsupportedCommand;
- (*pos)++;
+ pos++;
Error status;
if ((!read_number(line, pos, values[0], true)))
return Error::BadNumberFormat;
- if ((status = read_operation(line, pos, operators)) != Error::Ok)
+ if ((status = read_operation(line, pos, operators[0])) != Error::Ok)
return status;
- for (; operators[0] != NGCBinaryOp_RightBracket;) {
+ for (; operators[0] != Binary_RightBracket;) {
if ((!read_number(line, pos, values[stack_index], true)))
return Error::BadNumberFormat;
- if ((status = read_operation(line, pos, operators + stack_index)) != Error::Ok)
+ if ((status = read_operation(line, pos, operators[stack_index])) != Error::Ok)
return status;
if (precedence(operators[stack_index]) > precedence(operators[stack_index - 1]))
diff --git a/FluidNC/src/Expression.h b/FluidNC/src/Expression.h
index 9f139e389..4346aec9e 100644
--- a/FluidNC/src/Expression.h
+++ b/FluidNC/src/Expression.h
@@ -1,3 +1,3 @@
#pragma once
-Error expression(const char* line, size_t* pos, float& value);
-Error read_unary(const char* line, size_t* pos, float& value);
+Error expression(const char* line, size_t& pos, float& value);
+Error read_unary(const char* line, size_t& pos, float& value);
diff --git a/FluidNC/src/FileStream.cpp b/FluidNC/src/FileStream.cpp
index ca6a82290..5d32e73c8 100644
--- a/FluidNC/src/FileStream.cpp
+++ b/FluidNC/src/FileStream.cpp
@@ -68,6 +68,10 @@ FileStream::FileStream(FluidPath fpath, const char* mode) : Channel("file"), _mo
setup(mode);
}
+void FileStream::set_position(size_t pos) {
+ fseek(_fd, pos, SEEK_SET);
+}
+
void FileStream::save() {
_saved_position = position();
fclose(_fd);
diff --git a/FluidNC/src/FileStream.h b/FluidNC/src/FileStream.h
index 80cc63d53..fba720e82 100644
--- a/FluidNC/src/FileStream.h
+++ b/FluidNC/src/FileStream.h
@@ -55,6 +55,7 @@ class FileStream : public Channel {
size_t size();
size_t position();
+ void set_position(size_t);
// pollLine() is a required method of the Channel class that
// FileStream implements as a no-op.
diff --git a/FluidNC/src/Flowcontrol.cpp b/FluidNC/src/Flowcontrol.cpp
new file mode 100644
index 000000000..1dea3d127
--- /dev/null
+++ b/FluidNC/src/Flowcontrol.cpp
@@ -0,0 +1,374 @@
+// Copyright (c) 2024 - Mitch Bradley
+// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file.
+
+// Adapted from ngc_flowctrl.c in grblHAL - See https://github.com/grblHAL/core
+
+#include
+#include
+
+#include "Protocol.h"
+#include "Error.h"
+#include "Expression.h"
+#include "Parameters.h"
+#include "Job.h"
+#include
+
+#ifndef NGC_STACK_DEPTH
+# define NGC_STACK_DEPTH 10
+#endif
+
+typedef enum {
+ Op_NoOp = 0,
+ Op_If,
+ Op_ElseIf,
+ Op_Else,
+ Op_EndIf,
+ Op_Do,
+ Op_Continue,
+ Op_Break,
+ Op_While,
+ Op_EndWhile,
+ Op_Repeat,
+ Op_EndRepeat,
+ Op_Return,
+ Op_RaiseAlarm,
+ Op_RaiseError
+} ngc_cmd_t;
+
+typedef struct {
+ uint32_t o_label;
+ ngc_cmd_t operation;
+ JobSource* file;
+ size_t file_pos;
+ std::string expr;
+ uint32_t repeats;
+ bool skip;
+ bool handled;
+ bool brk;
+} ngc_stack_entry_t;
+
+std::stack context;
+
+std::map> commands = {
+ { "IF", Op_If },
+ { "ELSEIF", Op_ElseIf },
+ { "ELSE", Op_Else },
+ { "ENDIF", Op_EndIf },
+ { "DO", Op_Do },
+ { "CONTINUE", Op_Continue },
+ { "BREAK", Op_Break },
+ { "WHILE", Op_While },
+ { "ENDWHILE", Op_EndWhile },
+ { "REPEAT", Op_Repeat },
+ { "ENDREPEAT", Op_EndRepeat },
+ { "RETURN", Op_Return },
+ { "ALARM", Op_RaiseAlarm },
+ { "ERROR", Op_RaiseError },
+};
+
+static Error read_command(char* line, size_t& pos, ngc_cmd_t& operation) {
+ size_t start = pos;
+ while (isalpha(line[pos])) {
+ ++pos;
+ }
+ std::string_view key(line + start, pos - start);
+ auto it = commands.find(key);
+ if (it == commands.end()) {
+ return Error::FlowControlSyntaxError;
+ }
+ operation = it->second;
+ return Error::Ok;
+}
+
+static Error stack_push(uint32_t o_label, ngc_cmd_t operation, bool skip) {
+ ngc_stack_entry_t ent = { o_label, operation, Job::source(), 0, "", 0, skip, false, false };
+ context.push(ent);
+ return Error::Ok;
+}
+static bool stack_pull(void) {
+ if (context.empty()) {
+ return false;
+ }
+ context.pop();
+ return true;
+}
+void unwind_stack() {
+ if (context.empty()) {
+ return;
+ }
+ JobSource* file = context.top().file;
+ while (!context.empty() && context.top().file == file) {
+ stack_pull();
+ }
+}
+void flowcontrol_init(void) {
+ while (!context.empty()) {
+ stack_pull();
+ }
+}
+
+// Public functions
+
+Error flowcontrol(uint32_t o_label, char* line, size_t& pos, bool& skip) {
+ float value;
+ bool skipping;
+ ngc_cmd_t operation, last_op;
+
+ Error status;
+
+ if ((status = read_command(line, pos, operation)) != Error::Ok) {
+ return status;
+ }
+
+ skipping = !context.empty() && context.top().skip;
+ last_op = context.empty() ? Op_NoOp : context.top().operation;
+
+ switch (operation) {
+ case Op_If:
+ if (!skipping && (status = expression(line, pos, value)) == Error::Ok) {
+ stack_push(o_label, operation, !value);
+ context.top().handled = value;
+ }
+ break;
+
+ case Op_ElseIf:
+ if (last_op == Op_If || last_op == Op_ElseIf) {
+ if (o_label == context.top().o_label && !(context.top().skip = context.top().handled) && !context.top().handled &&
+ (status = expression(line, pos, value)) == Error::Ok) {
+ if (!(context.top().skip = !value)) {
+ context.top().operation = operation;
+ context.top().handled = true;
+ }
+ }
+ } else if (!skipping) {
+ status = Error::FlowControlSyntaxError;
+ }
+ break;
+
+ case Op_Else:
+ if (last_op == Op_If || last_op == Op_ElseIf) {
+ if (o_label == context.top().o_label) {
+ if (!(context.top().skip = context.top().handled)) {
+ context.top().operation = operation;
+ }
+ }
+ } else if (!skipping) {
+ status = Error::FlowControlSyntaxError;
+ }
+ break;
+
+ case Op_EndIf:
+ if (last_op == Op_If || last_op == Op_ElseIf || last_op == Op_Else) {
+ if (o_label == context.top().o_label) {
+ stack_pull();
+ }
+ } else if (!skipping) {
+ status = Error::FlowControlSyntaxError;
+ }
+ break;
+
+ case Op_Do:
+ if (Job::active()) {
+ if (!skipping) {
+ stack_push(o_label, operation, false);
+ context.top().file_pos = context.top().file->position();
+ }
+ } else {
+ status = Error::FlowControlNotExecutingMacro;
+ }
+ break;
+
+ case Op_While:
+ if (Job::active()) {
+ char* expr = line + pos;
+ if (!context.empty() && context.top().brk) {
+ if (last_op == Op_Do && o_label == context.top().o_label) {
+ stack_pull();
+ }
+ } else if (!skipping && (status = expression(line, pos, value)) == Error::Ok) {
+ if (last_op == Op_Do) {
+ if (o_label == context.top().o_label) {
+ if (value) {
+ context.top().file->set_position(context.top().file_pos);
+ } else {
+ stack_pull();
+ }
+ }
+ } else {
+ stack_push(o_label, operation, !value);
+ if (value) {
+ context.top().expr = expr;
+ context.top().file = Job::source();
+ context.top().file_pos = context.top().file->position();
+ }
+ }
+ }
+ } else {
+ status = Error::FlowControlNotExecutingMacro;
+ }
+ break;
+
+ case Op_EndWhile:
+ if (Job::active()) {
+ if (last_op == Op_While) {
+ if (!skipping && o_label == context.top().o_label) {
+ uint_fast8_t pos = 0;
+ if (!context.top().skip && (status = expression(context.top().expr.c_str(), pos, value)) == Error::Ok) {
+ if (!(context.top().skip = value == 0)) {
+ context.top().file->set_position(context.top().file_pos);
+ }
+ }
+ if (context.top().skip) {
+ stack_pull();
+ }
+ }
+ } else if (!skipping) {
+ status = Error::FlowControlSyntaxError;
+ }
+ } else {
+ status = Error::FlowControlNotExecutingMacro;
+ }
+ break;
+
+ case Op_Repeat:
+ if (Job::active()) {
+ if (!skipping && (status = expression(line, pos, value)) == Error::Ok) {
+ stack_push(o_label, operation, !value);
+ if (value) {
+ context.top().file = Job::source();
+ context.top().file_pos = context.top().file->position();
+ context.top().repeats = (uint32_t)value;
+ }
+ }
+ } else {
+ status = Error::FlowControlNotExecutingMacro;
+ }
+ break;
+
+ case Op_EndRepeat:
+ if (Job::active()) {
+ if (last_op == Op_Repeat) {
+ if (!skipping && o_label == context.top().o_label) {
+ if (context.top().repeats && --context.top().repeats) {
+ context.top().file->set_position(context.top().file_pos);
+ } else {
+ stack_pull();
+ }
+ }
+ } else if (!skipping) {
+ status = Error::FlowControlSyntaxError;
+ }
+ } else {
+ status = Error::FlowControlNotExecutingMacro;
+ }
+ break;
+
+ case Op_Break:
+ if (Job::active()) {
+ if (!skipping) {
+ while (o_label != context.top().o_label && stack_pull())
+ ;
+ last_op = !context.empty() ? Op_NoOp : context.top().operation;
+ if (last_op == Op_Do || last_op == Op_While || last_op == Op_Repeat) {
+ if (o_label == context.top().o_label) {
+ context.top().repeats = 0;
+ context.top().brk = context.top().skip = context.top().handled = true;
+ }
+ } else {
+ status = Error::FlowControlSyntaxError;
+ }
+ }
+ } else {
+ status = Error::FlowControlNotExecutingMacro;
+ }
+ break;
+
+ case Op_Continue:
+ if (Job::active()) {
+ if (!skipping) {
+ while (o_label != context.top().o_label && stack_pull())
+ ;
+ if (!context.empty() && o_label == context.top().o_label) {
+ switch (context.top().operation) {
+ case Op_Repeat:
+ if (context.top().repeats && --context.top().repeats) {
+ context.top().file->set_position(context.top().file_pos);
+ } else {
+ stack_pull();
+ }
+ break;
+
+ case Op_Do:
+ context.top().file->set_position(context.top().file_pos);
+ break;
+
+ case Op_While: {
+ uint_fast8_t pos = 0;
+ if (!context.top().skip && (status = expression(context.top().expr.c_str(), pos, value)) == Error::Ok) {
+ if (!(context.top().skip = value == 0)) {
+ context.top().file->set_position(context.top().file_pos);
+ }
+ }
+ if (context.top().skip) {
+ stack_pull();
+ }
+ } break;
+
+ default:
+ status = Error::FlowControlSyntaxError;
+ break;
+ }
+ } else {
+ status = Error::FlowControlSyntaxError;
+ }
+ }
+ } else {
+ status = Error::FlowControlNotExecutingMacro;
+ }
+ break;
+
+ case Op_RaiseAlarm:
+ if (!skipping && expression(line, pos, value) == Error::Ok) {
+ send_alarm((ExecAlarm)value);
+ }
+ break;
+
+ case Op_RaiseError:
+ if (!skipping && expression(line, pos, value) == Error::Ok) {
+ status = (Error)value;
+ }
+ break;
+
+ case Op_Return:
+ if (Job::active()) {
+#if 0
+ if (!skipping && grbl.on_macro_return) {
+ unwind_stack();
+ if (expression(line, pos, value) == Error::Ok) {
+ set_named_param("_value", value);
+ set_named_param("_value_returned", 1.0f);
+ } else {
+ set_named_param("_value_returned", 0.0f);
+ }
+ grbl.on_macro_return();
+ }
+#endif
+ } else {
+ status = Error::FlowControlNotExecutingMacro;
+ }
+ break;
+
+ default:
+ status = Error::FlowControlSyntaxError;
+ }
+
+ if (status != Error::Ok) {
+ flowcontrol_init();
+ skip = false;
+ log_debug(line);
+ } else {
+ skip = !context.empty() && context.top().skip;
+ }
+
+ return status;
+}
diff --git a/FluidNC/src/Flowcontrol.h b/FluidNC/src/Flowcontrol.h
new file mode 100644
index 000000000..500be1553
--- /dev/null
+++ b/FluidNC/src/Flowcontrol.h
@@ -0,0 +1,4 @@
+#pragma once
+
+void flowcontrol_init(void);
+Error flowcontrol(uint32_t o_label, char* line, size_t& pos, bool& skip);
diff --git a/FluidNC/src/GCode.cpp b/FluidNC/src/GCode.cpp
index 01d318d15..af856a239 100644
--- a/FluidNC/src/GCode.cpp
+++ b/FluidNC/src/GCode.cpp
@@ -18,6 +18,7 @@
#include "Machine/MachineConfig.h"
#include "Parameters.h"
+#include "Flowcontrol.h"
#include // memset
#include // sqrt etc.
@@ -68,6 +69,7 @@ void gc_init() {
gc_state.modal = modal_defaults;
gc_state.modal.override = config->_start->_deactivateParking ? Override::Disabled : Override::ParkingMotion;
coords[gc_state.modal.coord_select]->get(gc_state.coord_system);
+ flowcontrol_init();
}
// Sets g-code parser position in mm. Input in steps. Called by the system abort and hard
@@ -76,17 +78,68 @@ void gc_sync_position() {
motor_steps_to_mpos(gc_state.position, get_motor_steps());
}
+static bool decode_format_string(const char* comment, size_t& index, size_t len, const char*& format) {
+ // comment[index] is '%'
+ const char* f = comment + index;
+ int rem = len - index;
+ if (rem > 1 && f[1] == 'd') {
+ ++index;
+ format = "%.0f";
+ return true;
+ }
+ if (rem > 2 && f[1] == 'f') {
+ ++index;
+ format = "%.4f";
+ return true;
+ }
+ if (rem > 3 && f[1] == '.' && f[2] >= '0' && f[2] <= '9' && f[3] == 'f') {
+ static char fmt[5];
+ memcpy(fmt, f, 4);
+ fmt[4] = '\0';
+ index += 3;
+ format = fmt;
+ return true;
+ }
+ return false;
+}
+
static void gcode_comment_msg(char* comment) {
- char msg[80];
- const size_t offset = 4; // ignore "MSG_" part of comment
- size_t index = offset;
+ char msg[128];
+ size_t offset = strlen("MSG_");
+ size_t index;
if (strstr(comment, "MSG")) {
- while (index < strlen(comment)) {
- msg[index - offset] = comment[index];
- index++;
+ log_info("MSG," << &comment[offset]);
+ return;
+ }
+ offset = strlen("PRINT,"); // Same length as DEBUG,
+ bool isdebug = strncasecmp(comment, "DEBUG,", offset) == 0;
+ if (isdebug || strncasecmp(comment, "PRINT,", offset) == 0) {
+ const char* format = "%lf";
+ size_t msgindex = 0;
+ size_t len = strlen(comment);
+ for (index = offset; index < len; ++index) {
+ char c = comment[index];
+ if (c == '%') {
+ if (!decode_format_string(comment, index, len, format)) {
+ msg[msgindex++] = c;
+ }
+ } else if (c == '#') {
+ float number;
+ if (read_number(comment, index, number)) {
+ msgindex += sprintf(&msg[msgindex], format, number);
+ } else {
+ msg[msgindex++] = c;
+ }
+ } else {
+ msg[msgindex++] = c;
+ }
+ }
+ msg[msgindex] = '\0';
+ if (isdebug) {
+ log_debug("DEBUG," << msg);
+ } else {
+ log_info("PRINT," << msg);
}
- msg[index - offset] = 0; // null terminate
- log_info("GCode comment" << msg);
}
}
@@ -114,10 +167,19 @@ void collapseGCode(char* line) {
// Strip out ) that does not follow a (
break;
case '(':
+ if (gc_state.skip_blocks) {
+ *line = '\0';
+ return;
+ }
+
// Start the comment at the character after (
parenPtr = inPtr + 1;
break;
case ';':
+ if (gc_state.skip_blocks) {
+ *line = '\0';
+ return;
+ }
// NOTE: ';' comment to EOL is a LinuxCNC definition. Not NIST.
// gcode_comment_msg(inPtr + 1);
*outPtr = '\0';
@@ -231,20 +293,33 @@ Error gc_execute_line(char* line) {
pos = jogMotion ? 3 : 0; // Start parsing after `$J=` if jogging
while ((letter = line[pos]) != '\0') { // Loop until no more g-code words in line.
if (letter == '#') {
+ if (gc_state.skip_blocks) {
+ return Error::Ok;
+ }
pos++;
- if (!assign_param(line, &pos)) {
+ if (!assign_param(line, pos)) {
FAIL(Error::BadNumberFormat);
}
continue;
}
+
+ // XXX Should check that no other words are also present
+ if (bitnum_is_true(value_words, GCodeWord::O)) {
+ return flowcontrol(gc_block.values.o, line, pos, gc_state.skip_blocks);
+ }
+
// Import the next g-code word, expecting a letter followed by a value. Otherwise, error out.
if ((letter < 'A') || (letter > 'Z')) {
FAIL(Error::ExpectedCommandLetter); // [Expected word letter]
}
pos++;
- if (!read_number(line, &pos, value)) {
+ if (!read_number(line, pos, value)) {
FAIL(Error::BadNumberFormat); // [Expected word value]
}
+ if (gc_state.skip_blocks && letter != 'O') {
+ return Error::Ok;
+ }
+
// Convert values to smaller uint8 significand and mantissa values for parsing this word.
// NOTE: Mantissa is multiplied by 100 to catch non-integer command values. This is more
// accurate than the NIST gcode requirement of x10 when used for commands, but not quite
@@ -712,6 +787,14 @@ Error gc_execute_line(char* line) {
axis_word_bit = GCodeWord::N;
gc_block.values.n = int32_t(truncf(value));
break;
+ case 'O':
+ if (mantissa > 0) {
+ FAIL(Error::GcodeCommandValueNotInteger);
+ }
+ axis_word_bit = GCodeWord::O;
+ gc_block.values.o = int_value;
+ break;
+
case 'P':
axis_word_bit = GCodeWord::P;
gc_block.values.p = value;
@@ -1103,7 +1186,7 @@ Error gc_execute_line(char* line) {
case NonModal::GoHome0: // G28
case NonModal::GoHome1: // G30
// [G28/30 Errors]: Cutter compensation is enabled.
- // Retreive G28/30 go-home position data (in machine coordinates) from non-volatile storage
+ // Retrieve G28/30 go-home position data (in machine coordinates) from non-volatile storage
if (gc_block.non_modal_command == NonModal::GoHome0) {
coords[CoordIndex::G28]->get(coord_data);
} else { // == NonModal::GoHome1
@@ -1370,7 +1453,7 @@ Error gc_execute_line(char* line) {
(bitnum_to_mask(GCodeWord::X) | bitnum_to_mask(GCodeWord::Y) | bitnum_to_mask(GCodeWord::Z) |
bitnum_to_mask(GCodeWord::A) | bitnum_to_mask(GCodeWord::B) | bitnum_to_mask(GCodeWord::C))); // Remove axis words.
}
- clear_bits(value_words, bitnum_to_mask(GCodeWord::D));
+ clear_bits(value_words, (bitnum_to_mask(GCodeWord::D) | bitnum_to_mask(GCodeWord::O)));
if (value_words) {
FAIL(Error::GcodeUnusedWords); // [Unused words]
}
@@ -1467,7 +1550,7 @@ Error gc_execute_line(char* line) {
// NOTE: Pass zero spindle speed for all restricted laser motions.
if (!disableLaser) {
pl_data->spindle_speed = gc_state.spindle_speed; // Record data for planner use.
- } // else { pl_data->spindle_speed = 0.0; } // Initialized as zero already.
+ } // else { pl_data->spindle_speed = 0.0; } // Initialized as zero already.
// [5. Select tool ]: NOT SUPPORTED. Only tracks tool value.
// gc_state.tool = gc_block.values.t;
// [M6. Change tool ]:
@@ -1769,10 +1852,9 @@ Error gc_execute_line(char* line) {
}
gc_state.modal.program_flow = ProgramFlow::Running; // Reset program flow.
- perform_assignments();
+ return perform_assignments() ? Error::Ok : Error::ParameterAssignmentFailed;
// TODO: % to denote start of program.
- return Error::Ok;
}
//void grbl_msg_sendf(uint8_t client, MsgLevel level, const char* format, ...);
diff --git a/FluidNC/src/GCode.h b/FluidNC/src/GCode.h
index 6466add39..ddc3e6757 100644
--- a/FluidNC/src/GCode.h
+++ b/FluidNC/src/GCode.h
@@ -206,7 +206,9 @@ enum class GCodeWord : uint8_t {
A = 15,
B = 16,
C = 17,
- D = 18, // For debugging
+ O = 18,
+ D = 19, // For debugging
+
};
// GCode parser position updating flags
@@ -269,6 +271,7 @@ struct gc_values_t {
float ijk[3]; // I,J,K Axis arc offsets - only 3 are possible
uint8_t l; // G10 or canned cycles parameters
int32_t n; // Line number
+ uint32_t o; // Subroutine identifier - single-meaning word (not used by the core)
float p; // G10 or dwell parameters
float q; // M67
float r; // Arc radius
@@ -293,6 +296,7 @@ struct parser_state_t {
float coord_offset[MAX_N_AXIS]; // Retains the G92 coordinate offset (work coordinates) relative to
// machine zero in mm. Non-persistent. Cleared upon reset and boot.
float tool_length_offset; // Tracks tool length offset value when enabled.
+ bool skip_blocks; // Skipping due to flow control
};
extern parser_state_t gc_state;
diff --git a/FluidNC/src/Job.cpp b/FluidNC/src/Job.cpp
index 5e844fe90..5de670121 100644
--- a/FluidNC/src/Job.cpp
+++ b/FluidNC/src/Job.cpp
@@ -13,6 +13,10 @@ bool Job::active() {
return !job.empty();
}
+JobSource* Job::source() {
+ return job.empty() ? nullptr : job.top();
+}
+
// save() and restore() are use to close/reopen an SD file atop the job stack
// before trying to open a nested SD file. The reason for that is because
// the number of simultaneously-open SD files is limited to conserve RAM.
@@ -55,11 +59,11 @@ void Job::abort() {
}
}
-float Job::get_param(const std::string& name) {
- return job.top()->get_param(name);
+bool Job::get_param(const std::string& name, float& value) {
+ return job.top()->get_param(name, value);
}
-void Job::set_param(const std::string& name, float value) {
- job.top()->set_param(name, value);
+bool Job::set_param(const std::string& name, float value) {
+ return job.top()->set_param(name, value);
}
bool Job::param_exists(const std::string& name) {
return job.top()->param_exists(name);
diff --git a/FluidNC/src/Job.h b/FluidNC/src/Job.h
index 284c40ad9..c44684b77 100644
--- a/FluidNC/src/Job.h
+++ b/FluidNC/src/Job.h
@@ -11,12 +11,25 @@ class JobSource {
public:
JobSource(Channel* channel) : _channel(channel) {}
- float get_param(const std::string& name) { return _local_params[name]; }
- void set_param(const std::string& name, float value) { _local_params[name] = value; }
- bool param_exists(const std::string& name) { return _local_params.count(name) != 0; }
+ bool get_param(const std::string& name, float& value) {
+ auto it = _local_params.find(name);
+ if (it == _local_params.end()) {
+ return false;
+ }
+ value = it->second;
+ return true;
+ }
+ bool set_param(const std::string& name, float value) {
+ _local_params[name] = value;
+ return true;
+ }
+ bool param_exists(const std::string& name) { return _local_params.count(name) != 0; }
+
+ void save() { _channel->save(); }
+ void restore() { _channel->restore(); }
+ size_t position() { return _channel->position(); }
+ void set_position(size_t pos) { _channel->set_position(pos); }
- void save() { _channel->save(); }
- void restore() { _channel->restore(); }
Channel* channel() { return _channel; }
~JobSource() { delete _channel; }
@@ -31,14 +44,15 @@ class Job {
static bool active();
- static void save();
- static void restore();
- static void nest(Channel* in_channel, Channel* out_channel);
- static void unnest();
- static void abort();
+ static void save();
+ static void restore();
+ static void nest(Channel* in_channel, Channel* out_channel);
+ static void unnest();
+ static void abort();
+ static JobSource* source();
- static float get_param(const std::string& name);
- static void set_param(const std::string& name, float value);
+ static bool get_param(const std::string& name, float& value);
+ static bool set_param(const std::string& name, float value);
static bool param_exists(const std::string& name);
static Channel* channel();
};
diff --git a/FluidNC/src/NutsBolts.cpp b/FluidNC/src/NutsBolts.cpp
index 43e5669e3..41207b473 100644
--- a/FluidNC/src/NutsBolts.cpp
+++ b/FluidNC/src/NutsBolts.cpp
@@ -15,6 +15,26 @@
const int MAX_INT_DIGITS = 8; // Maximum number of digits in int32 (and float)
+static float uint_to_float(uint32_t intval, int exp) {
+ float fval = (float)intval;
+ // Apply decimal. Should perform no more than two floating point multiplications for the
+ // expected range of E0 to E-4.
+ if (fval != 0) {
+ while (exp <= -2) {
+ fval *= 0.01f;
+ exp += 2;
+ }
+ if (exp < 0) {
+ fval *= 0.1f;
+ } else if (exp > 0) {
+ do {
+ fval *= 10.0;
+ } while (--exp > 0);
+ }
+ }
+ return fval;
+}
+
// Extracts a floating point value from a string. The following code is based loosely on
// the avr-libc strtod() function by Michael Stumpf and Dmitry Xmelkov and many freely
// available conversion method examples, but has been highly optimized for Grbl. For known
@@ -22,18 +42,19 @@ const int MAX_INT_DIGITS = 8; // Maximum number of digits in int32 (and float)
// Scientific notation is officially not supported by g-code, and the 'E' character may
// be a g-code word on some CNC systems. So, 'E' notation will not be recognized.
// NOTE: Thanks to Radu-Eosif Mihailescu for identifying the issues with using strtod().
-bool read_float(const char* line, size_t* pos, float& result) {
- const char* ptr = line + *pos;
- unsigned char c;
- // Grab first character and increment pointer. No spaces assumed in line.
- c = *ptr++;
+bool read_float(const char* line, size_t& pos, float& result) {
+ const char* ptr = line + pos;
+
+ // Line is assumed to have no spaces
+
// Capture initial positive/minus character
+ char c = *ptr;
bool isnegative = false;
if (c == '-') {
+ ++ptr;
isnegative = true;
- c = *ptr++;
} else if (c == '+') {
- c = *ptr++;
+ ++ptr;
}
// Extract number into fast integer. Track decimal in terms of exponent value.
@@ -42,56 +63,37 @@ bool read_float(const char* line, size_t* pos, float& result) {
size_t ndigit = 0;
bool isdecimal = false;
while (1) {
- c -= '0';
- if (c <= 9) {
+ c = *ptr;
+ if (isdigit(c)) {
+ ++ptr;
ndigit++;
if (ndigit <= MAX_INT_DIGITS) {
if (isdecimal) {
exp--;
}
- intval = intval * 10 + c;
+ intval = intval * 10 + c - '0';
} else {
if (!(isdecimal)) {
exp++; // Drop overflow digits
}
}
- } else if (c == (('.' - '0') & 0xff) && !(isdecimal)) {
+ } else if (c == '.' && !(isdecimal)) {
+ ++ptr;
isdecimal = true;
} else {
break;
}
- c = *ptr++;
}
// Return if no digits have been read.
if (!ndigit) {
return false;
}
- // Convert integer into floating point.
- float fval;
- fval = (float)intval;
- // Apply decimal. Should perform no more than two floating point multiplications for the
- // expected range of E0 to E-4.
- if (fval != 0) {
- while (exp <= -2) {
- fval *= 0.01f;
- exp += 2;
- }
- if (exp < 0) {
- fval *= 0.1f;
- } else if (exp > 0) {
- do {
- fval *= 10.0;
- } while (--exp > 0);
- }
- }
- // Assign floating point value with correct sign.
- if (isnegative) {
- result = -fval;
- } else {
- result = fval;
- }
- *pos = ptr - line - 1; // Set pos to next statement
+ float fval = uint_to_float(intval, exp);
+
+ result = isnegative ? -fval : fval;
+
+ pos = ptr - line; // Set pos to next statement
return true;
}
diff --git a/FluidNC/src/NutsBolts.h b/FluidNC/src/NutsBolts.h
index c9fe1b644..bb482c775 100644
--- a/FluidNC/src/NutsBolts.h
+++ b/FluidNC/src/NutsBolts.h
@@ -54,7 +54,7 @@ const float INCH_PER_MM = (0.0393701f);
// Read a floating point value from a string. Line points to the input buffer, pos
// is the indexer pointing to the current character of the line, while float_ptr is
// a pointer to the result variable. Returns true when it succeeds
-bool read_float(const char* line, size_t* pos, float& result);
+bool read_float(const char* line, size_t& pos, float& result);
// Delay while checking for realtime characters and other events
bool dwell_ms(uint32_t milliseconds, DwellMode mode = DwellMode::Dwell);
diff --git a/FluidNC/src/Parameters.cpp b/FluidNC/src/Parameters.cpp
index 81edb97cb..bbd0ef498 100644
--- a/FluidNC/src/Parameters.cpp
+++ b/FluidNC/src/Parameters.cpp
@@ -126,7 +126,9 @@ bool set_numbered_param(ngc_param_id_t id, float value) {
gc_state.selected_tool = static_cast(value);
return true;
}
- if (id >= 31 && id <= 5000) {
+ if (id >= 1 && id <= 5000) {
+ // 1-30 are for subroutine arguments, but since we don't
+ // implement subroutines, we treat them the same as user params
user_params[id] = value;
return true;
}
@@ -199,24 +201,33 @@ struct param_ref_t {
};
std::vector> assignments;
-void set_config_item(const std::string& name, float result) {
+bool set_config_item(const std::string& name, float result) {
try {
Configuration::GCodeParam gci(name.c_str(), result, false);
config->group(gci);
- } catch (...) {}
+ if (gci.isHandled_) {
+ return true;
+ }
+ } catch (const AssertionFailed& ex) {
+ log_debug(ex.msg);
+ return false;
+ }
+ log_debug("Failed to set " << name);
+ return false;
}
bool get_config_item(const std::string& name, float& result) {
try {
Configuration::GCodeParam gci(name.c_str(), result, true);
config->group(gci);
-
if (gci.isHandled_) {
return true;
}
- log_debug(name << " is missing");
+ } catch (const AssertionFailed& ex) {
+ log_debug(ex.msg);
return false;
- } catch (...) { return false; }
+ }
+ return false;
}
int coord_values[] = { 540, 550, 560, 570, 580, 590, 591, 592, 593 };
@@ -354,6 +365,11 @@ bool get_system_param(const std::string& name, float& result) {
return false;
}
+bool system_param_exists(const std::string& name) {
+ float dummy;
+ return get_system_param(name, dummy);
+}
+
// The LinuxCNC doc says that the EXISTS syntax is like EXISTS[#<_foo>]
// For convenience, we also allow EXISTS[_foo]
bool named_param_exists(std::string& name) {
@@ -376,46 +392,50 @@ bool named_param_exists(std::string& name) {
if (got) {
return true;
}
- got = Job::param_exists(search);
- if (got) {
- return true;
- }
+ return global_named_params.count(search) != 0;
+ }
+ // If the name does not start with _ it is local so we look for a job-local parameter
+ // If no job is active, we treat the interpretive context like a local context
+ return Job::active() ? Job::param_exists(search) : global_named_params.count(search) != 0;
+}
+
+bool get_global_named_param(const std::string& name, float& value) {
+ auto it = global_named_params.find(name);
+ if (it == global_named_params.end()) {
+ return false;
}
- return global_named_params.count(search) != 0;
+ value = it->second;
+ return true;
}
-bool get_param(const param_ref_t& param_ref, float& result) {
+bool get_param(const param_ref_t& param_ref, float& value) {
auto name = param_ref.name;
if (name.length()) {
if (name[0] == '/') {
- return get_config_item(name, result);
+ return get_config_item(name, value);
}
- bool got;
if (name[0] == '_') {
- got = get_system_param(name, result);
- if (got) {
+ if (get_system_param(name, value)) {
return true;
}
- result = global_named_params[name];
- return true;
+ return get_global_named_param(name, value);
}
- result = Job::active() ? Job::get_param(name) : global_named_params[name];
- return true;
+ return Job::active() ? Job::get_param(name, value) : get_global_named_param(name, value);
}
- return get_numbered_param(param_ref.id, result);
+ return get_numbered_param(param_ref.id, value);
}
-bool get_param_ref(const char* line, size_t* pos, param_ref_t& param_ref) {
+bool get_param_ref(const char* line, size_t& pos, param_ref_t& param_ref) {
// Entry condition - the previous character was #
- char c = line[*pos];
+ char c = line[pos];
float result;
- // c is the first character and *pos still points to it
+ // c is the first character and pos still points to it
switch (c) {
case '#': {
// Indirection resulting in param number
param_ref_t next_param_ref;
- ++*pos;
+ ++pos;
if (!get_param_ref(line, pos, next_param_ref)) {
return false;
}
@@ -427,18 +447,22 @@ bool get_param_ref(const char* line, size_t* pos, param_ref_t& param_ref) {
return true;
case '<':
// Named parameter
- ++*pos;
- while ((c = line[*pos]) && c != '>') {
- ++*pos;
- param_ref.name += c;
+ ++pos;
+ while ((c = line[pos]) && c != '>') {
+ ++pos;
+ if (!isspace(c)) {
+ param_ref.name += toupper(c);
+ }
}
if (!c) {
+ log_debug("Missing >");
return false;
}
- ++*pos;
+ ++pos;
return true;
case '[': {
// Expression evaluating to param number
+ ++pos;
Error status = expression(line, pos, result);
if (status != Error::Ok) {
log_debug(errorString(status));
@@ -457,36 +481,48 @@ bool get_param_ref(const char* line, size_t* pos, param_ref_t& param_ref) {
}
}
-void set_param(const param_ref_t& param_ref, float value) {
- if (param_ref.name.length()) {
+bool set_named_param(const std::string& name, float value) {
+ global_named_params[name] = value;
+ return true;
+}
+
+bool set_param(const param_ref_t& param_ref, float value) {
+ if (param_ref.name.length()) { // Named parameter
auto name = param_ref.name;
if (name[0] == '/') {
- set_config_item(param_ref.name, value);
- return;
+ return set_config_item(param_ref.name, value);
}
if (name[0] != '_' && Job::active()) {
- Job::set_param(name, value);
- } else {
- global_named_params[name] = value;
+ return Job::set_param(name, value);
+ }
+ if (name[0] == '_' && system_param_exists(name)) {
+ log_debug("Attempt to set read-only parameter " << name);
+ return false;
}
- return;
+ return set_named_param(name, value);
}
- if (ngc_param_is_rw(param_ref.id)) {
- set_numbered_param(param_ref.id, value);
+ if (ngc_param_is_rw(param_ref.id)) { // Numbered parameter
+ return set_numbered_param(param_ref.id, value);
}
+ log_debug("Attempt to set read-only parameter " << param_ref.id);
+ return false;
}
// Gets a numeric value, either a literal number or a #-prefixed parameter value
-bool read_number(const char* line, size_t* pos, float& result, bool in_expression) {
- char c = line[*pos];
+bool read_number(const char* line, size_t& pos, float& result, bool in_expression) {
+ char c = line[pos];
if (c == '#') {
- ++*pos;
+ ++pos;
param_ref_t param_ref;
if (!get_param_ref(line, pos, param_ref)) {
return false;
}
- return get_param(param_ref, result);
+ if (get_param(param_ref, result)) {
+ return true;
+ }
+ log_debug("Undefined parameter " << param_ref.name);
+ return false;
}
if (c == '[') {
Error status = expression(line, pos, result);
@@ -503,7 +539,7 @@ bool read_number(const char* line, size_t* pos, float& result, bool in_expressio
return read_unary(line, pos, result) == Error::Ok;
}
if (c == '-') {
- ++*pos;
+ ++pos;
if (!read_number(line, pos, result, in_expression)) {
return false;
}
@@ -511,7 +547,7 @@ bool read_number(const char* line, size_t* pos, float& result, bool in_expressio
return true;
}
if (c == '+') {
- ++*pos;
+ ++pos;
return read_number(line, pos, result, in_expression);
}
}
@@ -519,17 +555,17 @@ bool read_number(const char* line, size_t* pos, float& result, bool in_expressio
}
// Process a #PREF=value assignment, with the initial # already consumed
-bool assign_param(const char* line, size_t* pos) {
+bool assign_param(const char* line, size_t& pos) {
param_ref_t param_ref;
if (!get_param_ref(line, pos, param_ref)) {
return false;
}
- if (line[*pos] != '=') {
+ if (line[pos] != '=') {
log_debug("Missing =");
return false;
}
- ++*pos;
+ ++pos;
float value;
if (!read_number(line, pos, value)) {
@@ -541,9 +577,13 @@ bool assign_param(const char* line, size_t* pos) {
return true;
}
-void perform_assignments() {
+bool perform_assignments() {
+ bool result = true;
for (auto const& [ref, value] : assignments) {
- set_param(ref, value);
+ if (!set_param(ref, value)) {
+ result = false;
+ }
}
assignments.clear();
+ return result;
}
diff --git a/FluidNC/src/Parameters.h b/FluidNC/src/Parameters.h
index 8f7a55e70..b9916d09c 100644
--- a/FluidNC/src/Parameters.h
+++ b/FluidNC/src/Parameters.h
@@ -6,7 +6,8 @@
#include
#include
-bool assign_param(const char* line, size_t* pos);
-bool read_number(const char* line, size_t* pos, float& value, bool in_expression = false);
-void perform_assignments();
+bool assign_param(const char* line, size_t& pos);
+bool read_number(const char* line, size_t& pos, float& value, bool in_expression = false);
+bool perform_assignments();
bool named_param_exists(std::string& name);
+bool set_named_param(const char* name, float value);