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;
}