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