AuxPreprocessor: ignore M-codes inside paren comments
Two bugs surfaced when macros got prose like:
(Composed from atoms: M102 = RELEASE (V1 on), M103 = CLAMP)
1. _ATC_M_RE.finditer was being run against the raw line, so the
M102/M103 *inside* the comment fired spurious release/clamp
hooks at file load.
2. The simple _PAREN_COMMENT_RE = re.compile(r'\(\[^)]*\)') is a
greedy non-nested match, so a header with a nested paren
(e.g. 'M102 = RELEASE (V1 on)') only stripped the inner
paren, leaving the trailing 'M103 = CLAMP)' visible to the
matcher.
Fix:
- Add _strip_comments() that walks the line tracking paren depth
and drops the trailing semicolon comment. Handles nested parens
correctly.
- Run _ATC_M_RE.finditer against the comment-stripped 'code'
instead of the raw line, so prose mentions are inert.
- Drop the original line's comments from the rewritten output;
keeping them around led to the M-codes being matched twice
(once stripped, once still in the trailing comment).
- Use _strip_comments in file_uses_aux too.
The grab.nc and drop.nc macros on the controller already had the
prose headers; they now preprocess correctly to clean
release / G4 / clamp and release / N x eject / Z0 / clamp
sequences.
This commit is contained in:
@@ -46,8 +46,42 @@ import tempfile
|
||||
|
||||
|
||||
# Strip line comments so we don't get fooled by "(M100 not really)".
|
||||
# Note this is a simple regex and doesn't handle nested parentheses
|
||||
# - which actually occur in real macro headers like
|
||||
# `(Composed from atoms: M102 = RELEASE (V1 on), M103 = CLAMP)`.
|
||||
# Use _strip_comments() below for a parser that does handle them.
|
||||
_PAREN_COMMENT_RE = re.compile(r'\([^)]*\)')
|
||||
|
||||
|
||||
def _strip_comments(line):
|
||||
"""Return `line` with paren comments and the trailing semicolon
|
||||
comment removed. Handles arbitrarily nested parentheses (RS274
|
||||
technically forbids them but real-world gcode comments often
|
||||
contain prose with parens, e.g. `(M102 = RELEASE (V1 on))`).
|
||||
|
||||
Returns just the executable code, with the original whitespace
|
||||
preserved between tokens."""
|
||||
out = []
|
||||
depth = 0
|
||||
i = 0
|
||||
n = len(line)
|
||||
while i < n:
|
||||
c = line[i]
|
||||
if c == ';' and depth == 0:
|
||||
break
|
||||
if c == '(':
|
||||
depth += 1
|
||||
i += 1
|
||||
continue
|
||||
if c == ')':
|
||||
if depth > 0: depth -= 1
|
||||
i += 1
|
||||
continue
|
||||
if depth == 0:
|
||||
out.append(c)
|
||||
i += 1
|
||||
return ''.join(out)
|
||||
|
||||
# ATC pneumatics M-codes mapped onto hook events. M101 is
|
||||
# deliberately unassigned (see header).
|
||||
_ATC_M_CODES = {
|
||||
@@ -135,8 +169,7 @@ class AuxPreprocessor(object):
|
||||
try:
|
||||
with open(path, 'r', encoding='utf-8', errors='replace') as f:
|
||||
for line in f:
|
||||
code = _PAREN_COMMENT_RE.sub('', line)
|
||||
code = code.split(';', 1)[0]
|
||||
code = _strip_comments(line)
|
||||
if _ATC_M_RE.search(code):
|
||||
return True
|
||||
if couple_active:
|
||||
@@ -327,8 +360,7 @@ class AuxPreprocessor(object):
|
||||
line = raw.rstrip('\n')
|
||||
|
||||
# Comment-only or blank lines pass through verbatim.
|
||||
code = _PAREN_COMMENT_RE.sub('', line)
|
||||
code = code.split(';', 1)[0]
|
||||
code = _strip_comments(line)
|
||||
if not code.strip():
|
||||
fout.write(raw)
|
||||
continue
|
||||
@@ -347,10 +379,14 @@ class AuxPreprocessor(object):
|
||||
if self._maybe_inject_a_down(code, fout):
|
||||
rewrote_any = True
|
||||
|
||||
# ATC M-codes (M100/M102/M103). Each ATC M-code on the line
|
||||
# is replaced with its (MSG,HOOK:<event>:) line and
|
||||
# stripped from the residual.
|
||||
atc_matches = list(_ATC_M_RE.finditer(line))
|
||||
# ATC M-codes (M100/M102/M103). Match against the
|
||||
# comment-stripped `code` so prose mentions like
|
||||
# `(M102 = RELEASE)` inside a comment don't spuriously
|
||||
# fire hooks. Each match emits a (MSG,HOOK:<event>:)
|
||||
# line; the M-code is stripped from the executable
|
||||
# residual but the original line's comments are kept
|
||||
# for log readability.
|
||||
atc_matches = list(_ATC_M_RE.finditer(code))
|
||||
if atc_matches:
|
||||
rewrote_any = True
|
||||
for m in atc_matches:
|
||||
@@ -359,18 +395,13 @@ class AuxPreprocessor(object):
|
||||
event = _ATC_M_CODES.get(num)
|
||||
if event:
|
||||
fout.write('(MSG,HOOK:%s:)\n' % event)
|
||||
line = _ATC_M_RE.sub('', line)
|
||||
code = _PAREN_COMMENT_RE.sub('', line)
|
||||
code = code.split(';', 1)[0]
|
||||
if not code.strip():
|
||||
# Nothing meaningful left; preserve any trailing
|
||||
# comment text but skip empty lines.
|
||||
rest = line.rstrip()
|
||||
if rest:
|
||||
fout.write(rest + '\n')
|
||||
continue
|
||||
# Other gcode remains on the line - emit it.
|
||||
fout.write(line + '\n')
|
||||
code_stripped = _ATC_M_RE.sub('', code).strip()
|
||||
if code_stripped:
|
||||
# Mixed line: keep the residual executable
|
||||
# gcode. Drop the comments to keep the
|
||||
# rewritten file tidy (the original line's
|
||||
# text already appears once as the input).
|
||||
fout.write(code_stripped + '\n')
|
||||
continue
|
||||
|
||||
# No rewrite needed.
|
||||
|
||||
Reference in New Issue
Block a user