class GCodeParser { static normalizeInput(raw) { if (raw === undefined || raw === null) { return ''; } if (Buffer.isBuffer(raw)) { raw = raw.toString('utf8'); } return String(raw).trim(); } static splitCommands(input) { if (!input) { return []; } // Preserve leading G/M on each segment. return input .trim() .split(/ (?=G|M)/i) .map(segment => segment.trim()) .filter(Boolean); } static parseCommand(commandString) { if (!commandString || commandString.length === 0) { return null; } const tokens = commandString.trim().split(/\s+/); if (tokens.length === 0) { return null; } const commandToken = tokens[0].toUpperCase(); if (!/^[GM]\d+/i.test(commandToken)) { return null; } const params = {}; for (let i = 1; i < tokens.length; i++) { const token = tokens[i].trim(); if (token.length === 0) { continue; } if (token.length === 1) { // Einzelner Buchstabe = Flag ohne Zahlenwert (z. B. 'R' in 'M114 R'). if (/^[A-Za-z]$/.test(token)) { params[token.toUpperCase()] = true; } continue; } const key = token.charAt(0).toUpperCase(); const valueString = token.substring(1); const value = Number(valueString); if (!Number.isNaN(value)) { params[key] = value; } } return { command: commandToken, params, raw: commandString.trim() }; } static parse(raw) { const input = this.normalizeInput(raw); if (!input) { return []; } if (input.startsWith('$J=')) { return this.parse(input.substring(3).trim()); } const commands = this.splitCommands(input) .map(commandString => this.parseCommand(commandString)) .filter(Boolean); return commands; } } module.exports = GCodeParser;