Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GCode quoted string support #16818

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Marlin/Configuration_adv.h
Original file line number Diff line number Diff line change
Expand Up @@ -2784,6 +2784,10 @@
*/
#define FASTER_GCODE_PARSER

#if ENABLED(FASTER_GCODE_PARSER)
//#define GCODE_QUOTED_STRINGS // Support for quoted string parameters
#endif

/**
* CNC G-code options
* Support CNC-style G-code dialects used by laser cutters, drawing machine cams, etc.
Expand Down
15 changes: 13 additions & 2 deletions Marlin/src/gcode/host/M115.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,26 @@
#endif

/**
* M115: Capabilities string
* M115: Capabilities string and extended capabilities report
* If a capability is not reported, hosts should assume
* the capability is not present.
*/
void GcodeSuite::M115() {

SERIAL_ECHOLNPGM(MSG_M115_REPORT);

#if ENABLED(EXTENDED_CAPABILITIES_REPORT)

// PAREN_COMMENTS
#if ENABLED(PAREN_COMMENTS)
cap_line(PSTR("PAREN_COMMENTS"), true);
#endif

// QUOTED_STRINGS
#if ENABLED(GCODE_QUOTED_STRINGS)
cap_line(PSTR("QUOTED_STRINGS"), true);
#endif

// SERIAL_XON_XOFF
cap_line(PSTR("SERIAL_XON_XOFF")
#if ENABLED(SERIAL_XON_XOFF)
Expand Down Expand Up @@ -171,6 +183,5 @@ void GcodeSuite::M115() {
#endif
);


#endif // EXTENDED_CAPABILITIES_REPORT
}
70 changes: 50 additions & 20 deletions Marlin/src/gcode/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,26 @@ void GCodeParser::reset() {
#endif
}

#if ENABLED(GCODE_QUOTED_STRINGS)

// Pass the address after the first quote (if any)
char* GCodeParser::unescape_string(char* &src) {
if (*src == '"') ++src; // Skip the leading quote
char * const out = src; // Start of the string
char *dst = src; // Prepare to unescape and terminate
for (;;) {
char c = *src++; // Get the next char
switch (c) {
case '\\': c = *src++; break; // Get the escaped char
case '"' : c = '\0'; break; // Convert bare quote to nul
}
if (!(*dst++ = c)) break; // Copy and break on nul
}
return out;
}

#endif

// Populate all fields by parsing a single line of GCode
// 58 bytes of SRAM are used to speed up seen/value
void GCodeParser::parse(char *p) {
Expand Down Expand Up @@ -229,17 +249,12 @@ void GCodeParser::parse(char *p) {
#if ENABLED(EXPECTED_PRINTER_CHECK)
case 16:
#endif
case 23: case 28: case 30: case 117: case 118: case 928: string_arg = p; return;
default: break;
}
/*
#if ENABLED(CANCEL_OBJECTS)
if (letter == 'O') switch (codenum) {
case 1: string_arg = p; return;
case 23: case 28: case 30: case 117: case 118: case 928:
string_arg = unescape_string(p);
return;
default: break;
}
#endif
*/

#if ENABLED(DEBUG_GCODE_PARSER)
const bool debug = codenum == 800;
#endif
Expand All @@ -252,21 +267,31 @@ void GCodeParser::parse(char *p) {
* This allows M0/M1 with expire time to work: "M0 S5 You Win!"
* For 'M118' you must use 'E1' and 'A1' rather than just 'E' or 'A'
*/
#if ENABLED(GCODE_QUOTED_STRINGS)
bool quoted_string_arg = false;
#endif
string_arg = nullptr;
while (const char code = *p++) { // Get the next parameter. A NUL ends the loop
while (const char param = *p++) { // Get the next parameter. A NUL ends the loop

// Special handling for M32 [P] !/path/to/file.g#
// The path must be the last parameter
if (code == '!' && letter == 'M' && codenum == 32) {
if (param == '!' && letter == 'M' && codenum == 32) {
string_arg = p; // Name starts after '!'
char * const lb = strchr(p, '#'); // Already seen '#' as SD char (to pause buffering)
if (lb) *lb = '\0'; // Safe to mark the end of the filename
return;
}

#if ENABLED(GCODE_QUOTED_STRINGS)
if (!quoted_string_arg && param == '"') {
quoted_string_arg = true;
string_arg = unescape_string(p);
}
#endif

// Arguments MUST be uppercase for fast GCode parsing
#if ENABLED(FASTER_GCODE_PARSER)
#define PARAM_TEST WITHIN(code, 'A', 'Z')
#define PARAM_TEST WITHIN(param, 'A', 'Z')
#else
#define PARAM_TEST true
#endif
Expand All @@ -275,16 +300,22 @@ void GCodeParser::parse(char *p) {

while (*p == ' ') p++; // Skip spaces between parameters & values

const bool has_num = valid_float(p);
#if ENABLED(GCODE_QUOTED_STRINGS)
const bool is_str = (*p == '"'), has_val = is_str || valid_float(p);
char * const valptr = has_val ? is_str ? unescape_string(p) : p : nullptr;
#else
const bool has_val = valid_float(p);
char * const valptr = has_val ? p : nullptr;
#endif

#if ENABLED(DEBUG_GCODE_PARSER)
if (debug) {
SERIAL_ECHOPAIR("Got letter ", code, " at index ", (int)(p - command_ptr - 1));
if (has_num) SERIAL_ECHOPGM(" (has_num)");
SERIAL_ECHOPAIR("Got param ", param, " at index ", (int)(p - command_ptr - 1));
if (has_val) SERIAL_ECHOPGM(" (has_val)");
}
#endif

if (!has_num && !string_arg) { // No value? First time, keep as string_arg
if (!has_val && !string_arg) { // No value? First time, keep as string_arg
string_arg = p - 1;
#if ENABLED(DEBUG_GCODE_PARSER)
if (debug) SERIAL_ECHOPAIR(" string_arg: ", hex_address((void*)string_arg)); // DEBUG
Expand All @@ -296,7 +327,7 @@ void GCodeParser::parse(char *p) {
#endif

#if ENABLED(FASTER_GCODE_PARSER)
set(code, has_num ? p : nullptr); // Set parameter exists and pointer (nullptr for no number)
set(param, valptr); // Set parameter exists and pointer (nullptr for no value)
#endif
}
else if (!string_arg) { // Not A-Z? First time, keep as the string_arg
Expand Down Expand Up @@ -359,7 +390,7 @@ void GCodeParser::unknown_command_warning() {
if (seen(c)) {
SERIAL_ECHOPAIR("Code '", c); SERIAL_ECHOPGM("':");
if (has_value()) {
SERIAL_ECHOPAIR(
SERIAL_ECHOLNPAIR(
"\n float: ", value_float(),
"\n long: ", value_long(),
"\n ulong: ", value_ulong(),
Expand All @@ -374,8 +405,7 @@ void GCodeParser::unknown_command_warning() {
);
}
else
SERIAL_ECHOPGM(" (no value)");
SERIAL_ECHOLNPGM("\n");
SERIAL_ECHOLNPGM(" (no value)");
}
}
}
Expand Down
10 changes: 10 additions & 0 deletions Marlin/src/gcode/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,12 @@ class GCodeParser {
return SEEN_TEST('X') || SEEN_TEST('Y') || SEEN_TEST('Z') || SEEN_TEST('E');
}

#if ENABLED(GCODE_QUOTED_STRINGS)
static char* unescape_string(char* &src);
#else
FORCE_INLINE static char* unescape_string(char* &src) { return src; }
#endif

// Populate all fields by parsing a single line of GCode
// This uses 54 bytes of SRAM to speed up seen/value
static void parse(char * p);
Expand All @@ -223,6 +229,9 @@ class GCodeParser {
// Seen a parameter with a value
static inline bool seenval(const char c) { return seen(c) && has_value(); }

// Float removes 'E' to prevent scientific notation interpretation
static inline char* value_string() { return value_ptr; }

// Float removes 'E' to prevent scientific notation interpretation
static inline float value_float() {
if (value_ptr) {
Expand Down Expand Up @@ -369,6 +378,7 @@ class GCodeParser {
void unknown_command_warning();

// Provide simple value accessors with default option
static inline char* stringval(const char c, char * const dval=nullptr) { return seenval(c) ? value_string() : dval; }
static inline float floatval(const char c, const float dval=0.0) { return seenval(c) ? value_float() : dval; }
static inline bool boolval(const char c, const bool dval=false) { return seenval(c) ? value_bool() : (seen(c) ? true : dval); }
static inline uint8_t byteval(const char c, const uint8_t dval=0) { return seenval(c) ? value_byte() : dval; }
Expand Down
Loading