C++ 扫描 Unicode 字符

C++ 扫描 Unicode 字符,记录字符的行列位置,记录字符的 Unicode 编码和 UTF8 编码,为进一步扫描 Token 做准备。

#include <iostream>
#include <fstream>

using namespace std;

class Scanner {
    ifstream f;  // 源文件
public:
    uint32_t w;  // 当前读取的字符(宽字符)
    char u[5];   // 当前读取的字符的 UTF8 编码
    int len;     // 当前读取的字符的 UTF8 编码长度
    int row;     // 当前字符所在的行数
    int col;     // 当前字符所在的列数

    Scanner(string filename);
    ~Scanner();
    void nextchar();
};

Scanner::Scanner(string filename) {
    // 打开文件
    f.open(filename);
    if (!f.is_open()) {
        perror("Scanner -> ifstream -> open()");
        exit(1);
    }

    // 跳过 UTF8 BOM(0xEFBBBF)
    if (f.get() != 0xEF || f.get() != 0xBB || f.get() != 0xBF) {
        f.seekg(0, ios::beg);
    }

    w = (uint32_t)-1;
    u[4] = 0;  // 字符串结尾的 \0
    row = 1;
    col = 0;
}

Scanner::~Scanner(){
    f.close();
}

// 读取下一个字符,结果存入 Scanner::w 中,utf8 编码存入 Scanner::u 中
void Scanner::nextchar() {
	unsigned char c;    // UTF8 码点,涉及位运算,必须使用无符号数

    // 如果上次读取的是换行符,则进行换行处理
    if (w == 10) {
        row++;
        col = 0;
    }

	c = f.get();

    if (f.eof()) {
        // 保证文件尾部有一个换行符
        if (w == 0 || w == 10) {
            w = 0;
            len = 0;
        } else {
            w = 10;
            u[0] = 10;
            len = 1;
        }
    } else {
		if (c < 0b10000000) {
			// 单字节编码
			w = c;
            // 保存 UTF8 码点
            u[0] = c;
            len = 1;
            // 将 \r\n 合并为 \n,将 \r 替换为 \n
            if (w == 13) {
                if (f.peek() == 10) {
                    f.get();
                    // 保存 UTF8 码点
                    u[1] = 10;
                    len = 2;
                }
                w = 10;
            }
		} else {
			// 多字节编码,获取编码长度
			if (c > 0b11110100) {
				cout << (uint32_t)c << endl;
				// 超出可用 Unicode 范围 0x10FFFF
				// 11110100_10001111_10111111_10111111
				fprintf(stderr, "Invalid unicode range\n");
				exit(1);
			} else if (c >= 0b11110000) {
				len = 4;
			} else if (c >= 0b11100000) {
				len = 3;
			} else if (c >= 0b11000000) {
				len = 2;
			} else {
				// 首字节不能小于 0b11000000
				fprintf(stderr, "Invalid utf8 leading code");
				exit(1);
			}
            u[0] = c;  // 保存 UTF8 码点
			// 通过左移再右移的方法去掉首字节中的 UTF8 标记
			c = c << (len + 1);
			w = c >> (len + 1);

			// 处理后续 UTF8 编码
			for(int i = 1; i < len; i++) {
				c = f.get();
				// 如果 f 到达 eof,则 c 会返回 255,刚好匹配下面的错误检查
				// 后续编码必须是 0b10xxxxxx 格式
				if (c >= 0b11000000) {
					fprintf(stderr, "Invalid utf8 tailing code");
					exit(1);
				}
                u[i] = c;            // 保存 UTF8 码点
				c = c & 0b00111111;  // 去掉 UTF8 标记
				w = w << 6;          // 腾出 6 个 bit 的位置
				w += c;              // 将去掉了 UTF8 标记的编码合并进来
			}
		}
	}
    col++;
    u[len] = 0;
}

int main() {
    Scanner s("test1.txt");

    s.nextchar();
    while (s.w != 0) {
        printf("[%.2d,%.2d,%d]%s", s.row, s.col, s.w, s.u);
        s.nextchar();
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值