2. 处理注释和空白

任务目标

扩展手写G代码解析器,使其能够正确处理G代码行中的注释与空白字符。注释以分号;开头直至行尾;空白包括空格与制表符。解析器需在词法分析阶段剥离注释与空白,输出干净的命令参数序列,为后续指令分发做准备。

实现方案

在现有解析器基础上增加一个cleanLine()预处理函数:先截断分号注释,再按空白字符切分剩余部分,返回参数数组。该函数不修改原始行,保持解析器无状态。核心逻辑约20行,无外部依赖。

关键完整代码

// gcode_parser.h
#ifndef GCODE_PARSER_H
#define GCODE_PARSER_H

#include <string>
#include <vector>

// 去除注释与多余空白,返回参数列表(不含空串)
std::vector<std::string> cleanLine(const std::string& raw);

// 判断字符串是否为合法G代码指令(如 "G01"、"M03")
bool isCommand(const std::string& );

#endif // GCODE_PARSER_H
// gcode_parser.cpp
#include "gcode_parser.h"
#include <cctype>

std::vector<std::string> cleanLine(const std::string& raw) {
    std::vector<std::string> tokens;
    std::string current;

    for (size_t i = 0; i < raw.size(); ++i) {
        char c = raw[i];

        // 注释开始:直接终止处理
        if (c == ';') break;

        // 空白(空格/制表符):切分
        if (c == ' ' || c == '\t') {
            if (!current.empty()) {
                tokens.push_back(current);
                current.clear();
            }
            continue;
        }

        // 普通字符:累积
        current += c;
    }

    // 收尾最后一个
    if (!current.empty()) {
        tokens.push_back(current);
    }

    return tokens;
}

bool isCommand(const std::string& ) {
    if (.size() < 2) return false;
    char letter = [0];
    if (letter != 'G' && letter != 'M') return false;
    for (size_t i = 1; i < .size(); ++i) {
        if (!std::isdigit(static_cast<unsigned char>([i]))) return false;
    }
    return true;
}
// main.cpp —— 最小验证入口
#include "gcode_parser.h"
#include <iostream>

int main() {
    std::string lines[] = {
        "G01 X10 Y20 F300 ; 直线切削",
        "  M03  S1000\t; 主轴正转",
        "G00 X0 Y0",
        "; 整行注释,应被完全忽略",
        "G28 ; 回原点"
    };

    for (const auto& raw : lines) {
        auto tokens = cleanLine(raw);
        std::cout << "原始: [" << raw << "]\n";
        std::cout << "清洗: ";
        for (const auto& t : tokens) {
            std::cout << "[" << t << "] ";
        }
        std::cout << "\n";

        // 验证第一个是否为合法指令
        if (!tokens.empty() && isCommand(tokens[0])) {
            std::cout << "指令: " << tokens[0] << "\n";
        } else {
            std::cout << "指令: (无)\n";
        }
        std::cout << "---\n";
    }
    return 0;
}

编译与运行

g++ -std=c++11 -Wall -o gcode_test main.cpp gcode_parser.cpp
./gcode_test

验证方法与通过条件

运行程序后,逐行检查输出:

  • 第1行应输出清洗: [G01] [X10] [Y20] [F300],指令为G01——注释; 直线切削被完全剥离;
  • 第2行开头有2个空格、中间有制表符,应输出清洗: [M03] [S1000],指令为M03——所有前导/中间空白被压缩;
  • 第3行无注释无多余空白,输出[G00] [X0] [Y0];
  • 第4行整行为注释,输出清洗:(空),指令为(无);
  • 第5行输出[G28],指令为G28。

若以上5项输出与预期一致,说明注释剥离与空白压缩逻辑正确,可进入后续指令分发阶段。

常见问题

  • 注释符出现在字符串中间:G代码标准中分号只作注释起始,不处理引号转义,因此直接截断即可。
  • 连续多个空格:代码通过current.empty()判断避免产生空,无需额外去重逻辑。
  • 大小写敏感:G代码指令通常大写,isCommand()仅识别大写G/M开头;若需兼容小写,可在比较前统一toupper()。